1

Motivation

I am maintaining an app that is white-labelled for numerous separate brands, which vary mainly in style but also sometimes in core UX. The current (Backbone) solution involves keeping shared code in a separate repo and then building the separate apps with Grunt, with much of the style code and some view overrides for each project living in its own folder. We simply run all the grunt tasks one after the other using a shell script. We're going to build new versions of this thing in React going forward and want to minimize duplicate code, which has now become a major problem in the legacy version.

Desired outcome

The React Native packager builds two versions of its app at the same time. It looks at an import statement like import ComponentA from './ComponentA.js' and goes looking for either ComponentA.android.js or ComponentA.ios.js first, then falls back to importing ComponentA.js if it doesn't find a platform-specific one. I would like to replicate this behavior in Webpack. So I would like to have a folder that looks like this:

react_clients/src/components
  |_ ComponentB.js // import ComponentA from './ComponentA.js';
  |_ ComponentA.js
  |_ ComponentA.brand1.js
  |_ ComponentA.brand2.js

Webpack should build ComponentB.js as follows:

  • brand1.bundle.js imports from ComponentA.brand1.js
  • brand2.bundle.js imports from ComponentA.brand2.js
  • brand3.bundle.js and brand4.bundle.js import from ComponentA.js

This would also apply to styles, ideally with the same naming convention.

If necessary, Webpack could be run separately for each version, either using different webpack.config files or accepting command line arguments. The key thing is to avoid duplicating application code.

Current code

The starting point for Webpack is a freshly-generated and ejected create-react-app project.


PS: Apologies in advance if this turns out to be a duplicate but this has been a very tricky question to research. I suspect the answer will have something to do with an advanced configuration of https://webpack.js.org/configuration/resolve/ but can't figure it out yet.

1 Answer 1

1

Alright folks here's what I ended up doing:

Dev

In .env.development I specify a variable for the name of the project I want to do dev on:

REACT_APP_VERSION_NAME=brand1

Then in webpack.config.dev.js I take advantage of module resolution to achieve the behavior described above:

const JS_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.js`;
const STYLE_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.pcss`;
const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];

...
module.exports = {
...
  extensions,
...
}

Then in the code I can simply do the following:

import ComponentA from './componentA';
import Styles from './styles';

And everything works as expected.


Production

I don't specify REACT_APP_VERSION_NAME in .env.production. Instead the relevant config files export functions, and I iterate over the versions I want to build.

First, I created a separate version of config/paths.js that exports a function instead of a static object:

module.exports = function(projectName) {
  return {
    ...
    appBuild: resolveApp('build/' + projectName),
    ...
  };
}

And my webpack.config.prod.js looks like this:

...
const getPaths = require('./paths.prod');
...

module.exports = function(projectName) {
  const paths = getPaths(projectName);

  const JS_PROJECT_EXTENSION = `.${projectName}.js`;
  const STYLE_PROJECT_EXTENSION = `.${projectName}.pcss`;
  const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];

  ...
  return {
    ...
    output: {
      ...
      filename: projectName + '-assets/js/[name].[chunkhash:8].js',
      chunkFilename: projectName + '-assets/js/[name].[chunkhash:8].chunk.js',
      ...
    }
    ... [etc, adding projectName to any output that needs to be built separately]
  };
}

Finally, just wrap most of the action in scripts/build.js in a loop:

...
[various imports]
...

process.argv[2].split(' ').forEach(projectName => {
  const config = require('../config/webpack.config.prod')(projectName);
  const paths = require('../config/paths.prod')(projectName);
  ...
  [rest of build.js as normal]
}

After that it's just a matter of pointing your server at the right files for each version, and running yarn build "brand1 brand2" when you want to build.

Going to accept this answer since it's working for me for now but would love to hear about potential improvements from anyone who comes across it in the future.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.