2

I have a product made with React.js (sth like CRM) which I would like to distribute to many of my customers but with client-specific components (override the common ones).

Source folder structure of main codebase looks like this:

...
/src
├── components
│   ├── Sidebar
│   ├── Navbar
│   ├── Buttons
│   ├── Chart
│   └── __tests__
├── containers
│   ├── About
│   ├── App
│   ├── Chat
│   ├── ...
├── helpers
├── redux
│       ...
├── theme
|       ...
└── vendor
...
/custom-src
├── clientA
│   ├── components
│   ├── containers
│   └── theme
└── clientB
    └── components


...

But, each customer wants custom designed components for their CRM like custom Navbar, custom Sidebar, etc.. + custom UI theme (CSSs).

I don't want to have multiple codebases but also I don't want to distribute each client the same bundled code which will also include custom components of other clients.

Let's say I have clientA. He has some custom made components (which overrides the common ones), custom made containers and specific theme. Til now I was using bash script to merge /src folder with /custom-src/<client> folder but this approach does not seem right to me and also I have to make temp folder outside the working directory which is not very practical.

Does somebody know how can do this using webpack, which I already use for code bundling?

My current webpack configuration looks like this:

{
    devtool: 'inline-source-map',
    context: path.resolve(__dirname, '..'),
    entry: {
        'main': [
            'webpack-hot-middleware/client?path=http://' + host + ':' + port + '/__webpack_hmr',
            './src/theme/loader!./webpack/styles.js',
            'font-awesome-webpack!./src/theme/font-awesome.config.js',
            './src/client.js'
        ]
    },  
    output: {
        path: assetsPath,
        filename: '[name]-[hash].js',
        chunkFilename: '[name]-[chunkhash].js',
        publicPath: 'http://' + host + ':' + port + '/dist/'
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loaders: ['babel?' + JSON.stringify(babelLoaderQuery), 'eslint-loader']
            },
            {test: /\.json$/, loader: 'json-loader'},
            {
                test: /\.less$/,
                loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap'
            },
            {
                test: /\.scss$/,
                loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap'
            },
            {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff"},
            {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff"},
            {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream"},
            {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file"},
            {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml"},
            {
                test: webpackIsomorphicToolsPlugin.regular_expression('images'),
                loader: 'url-loader?limit=10240&name=[hash:6].[ext]'
            }
        ]
    },
    progress: true,
    resolve: {
        modulesDirectories: [
            'src',
            'node_modules'
        ],
        extensions: ['', '.json', '.js', '.jsx']
    },
    plugins: [
        // hot reload
        new webpack.HotModuleReplacementPlugin(),
        new webpack.IgnorePlugin(/webpack-stats\.json$/),
        new webpack.DefinePlugin({
            __CLIENT__: true,
            __SERVER__: false,
            __DEVELOPMENT__: true
        }),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        }),
        webpackIsomorphicToolsPlugin.development()
    ]
}
ememem
  • 316
  • 1
  • 3
  • 15
  • You can create directory like src/components-clientA src/components-clientB and then create soft-links while building with webpack. – Umang Gupta Feb 05 '17 at 19:01
  • 1
    That's almost the same approach as I do now, I really would love to use webpack. But thx anyway – ememem Feb 05 '17 at 20:05

1 Answers1

0

So I solved this thing using webpack's resolve.alias functionality with dynamically added filenames into it. Here's the code:

// webpack.config.js
let variation = require('./variation')("clientA");
...
let alias = Object.assign(variation, {
    "components" : path.resolve(__dirname, '../src/components'),
    "config" : path.resolve(__dirname, '../src/config'),
    "containers" : path.resolve(__dirname, '../src/containers'),
    "helpers" : path.resolve(__dirname, '../src/helpers'),
    "theme" : path.resolve(__dirname, '../src/theme'),
    "utils" : path.resolve(__dirname, '../src/utils'),
    "vendor" : path.resolve(__dirname, '../src/vendor')
});
...
module.exports = {
    ...
    resolve: {
            ...
            alias: alias,
            ...
        },
    ...
}

.

// variation.js
const fs = require('fs');
const path = require('path');

const walkSync = function (dir, filelist, base) {
    const files = fs.readdirSync(dir);
    const extension = /\.(js|jsx)$/i;
    filelist = filelist || {};
    files.forEach(function (file) {
        const dirname = dir.replace(base, '').substr(1);
        const fullname = dir + '/' + file;
        const filename = file.replace(/(.*)\.[^.]+$/, '$1');

        if (fs.statSync(dir + '/' + file).isDirectory()) {
            filelist = walkSync(dir + '/' + file, filelist, base);
        } else {
            if (extension.test(file)) {
                filelist[dirname + '/' + filename] = fullname;
            } else {
                filelist[dirname + '/' + file] = fullname;
            }

        }
    });
    return filelist;
};


module.exports = function(variation){
    const dirname = path.resolve(__dirname, '../custom/' + variation);
    const aliasComponents = walkSync(dirname + "/components", {}, dirname);
    const aliasContainers = walkSync(dirname + "/containers", {}, dirname);


    return Object.assign({}, aliasComponents, aliasContainers);
};

I hope someone will find it helpful.

ememem
  • 316
  • 1
  • 3
  • 15