loading...

Implementing feature toggles for a React App using Webpack

avraammavridis profile image Avraam Mavridis ・1 min read

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.

Discussion

pic
Editor guide
Collapse
ramsunvtech profile image
Venkat.R

Avraam, it was written nicely on the feature toggle. Lets take a example, instead of Feature toggle take it as Country Toggles.

Sample Use Case 1, where we need to list the city
USA
Show City Name as null for state 1 and state 2 and else return city
INDIA.
Show city details as always
BELGIUM
Show city with --City--

Below is the code samples, whats your taken on this

const {
  cityName,
  cityKey
} = country({
 US: async () => {
  const response = await apiCall();
  if (state1 || state2) {
     return null;
  }

  return {
     cityName: response.city,
     cityKey: response.cityKey,
  };
 },
IN: async () => {
  const response = await apiCall();
    return {
     cityName: response.city,
     cityKey: response.cityKey,
  };
},
BE: async () => {
  const response = await apiCall();
    return {
     cityName: `--${response.city}`,
     cityKey: response.cityKey,
  };
});
Collapse
sarokrishnan profile image
Saro

Using DefinePlugin for my feature flag or toggle. here is the code reference.

new webpack.DefinePlugin({
'NICE_FEATURE': JSON.stringify(false),
})
It compiles and app can refer the value correctly. If I update the value to true and after I run npm i and then I still see the old value. Can you please help here.