Implementing feature toggles for a React App using Webpack

Feature switches/toggles are an important technique that can help us deploy code in various environments under different conditions without blocking other developers in the team (or other teams) from releasing their features. Martin Fowler has an extensive article on the topic, I won’t focus on the theoretical part of it, I would like to show an implementation of feature flags in a react project.

Let’s assume that we are working on a React Project where we have 4 environments,

  • Development (local development)
  • Testing (where our tests are running aka NODE_ENV=test)
  • Staging (Production like environment, minification bundling etc)
  • Production

and let’s assume that we have a super experimental component that we would like to display on our Staging environment so we can QA it, but not on production.

class ExperimentalButton extends Component {

  render() {
    return <Button.Primary {...this.props} />;
  }
}

Webpack has a plugin that can help us create feature flags, its called DefinePlugin

new webpack.DefinePlugin({
  'process.env': {
    SHOW_EXPERIMENTAL_BUTTON: true
  }
});

Usually in our projects we have webpack.config.dev.js and webpack.config.production.js, but not a config for staging, since we want the code on staging and production to be identical usually we deploy the production build there. Also we don’t pass our source code through Webpack before running tests. So how would we differentiate between staging/production/development but avoid creating a webpack.config.staging.js?

In my case I created a featureToggles.json which looks like this:

{
  "test": {
    "showExperimentalButton": true,
  },
  "development": {
    "showExperimentalButton": true,
  },
  "staging": {
    "showExperimentalButton": true,
  },
  "production": {
    "showExperimentalButton": false,
  }
}

To differentiate among stating/production/development in my package.json I pass a flag to the script

    "build:production": "npm run build",
    "build:staging": "npm run build -- --staging",
    "build:development": "npm run build -- --development",

In my webpack.config.js (shared configuration options for every environment) I do:

const featureSwitches = require('./featureSwitches.json');

let features_env = process.argv[2] === '--staging' ? 'staging' : 'production';
features_env = process.argv[2] === '--development' ? 'development' : features_env;

...
...

new webpack.DefinePlugin({
  'process.env': {
    ...featureSwitches[features_env]
  }
});    

To display/hide our component we will do something like

{ process.env.showExperimentalButton ? <ExperimentalButton /> : null }

(Or we can go a step further and create a wrapper component <FeatureToggle feature='showExperimentalButton'>.)

There is a problem though, the previous is not working on the testing environment, since the code is not being passed through Webpack. So we are unable to write unit tests for the component. First we need to tell Jest to setup a few things before running the tests, we can do that with the setupTestFrameworkScriptFile option and create a jest.init.js.

setupTestFrameworkScriptFile: '<rootDir>/jest.init.js',

In our jest.init.js file we will do:

const featureSwitches = require('./config/featureSwitches');


Object.keys(featureSwitches.test).forEach((key) => {
  process.env[key] = featureSwitches.test[key];
});

Now we are able to run unit tests for our experimental component.

Published 25 Oct 2018

Engineering Manager. Opinions are my own and not necessarily the views of my employer.
Avraam Mavridis on Twitter