Feature Flags plugin for Webpack.
Feature Toggles (often also refered to as Feature Flags) are a powerful technique, allowing teams to modify system behavior without changing code. They fall into various usage categories, and it's important to take that categorization into account when implementing and managing toggles. Toggles introduce complexity. We can keep that complexity in check by using smart toggle implementation practices and appropriate tools to manage our toggle configuration, but we should also aim to constrain the number of toggles in our system. (c) Martin Fowler
FeatureFlagsPlugin is an abstraction over DefinePlugin which is supposed to be used for FeatureFlags within Webpack.
FeatureFlagsPlugin was constructed exact for FeatureFlags. It has two key features:
- it simplifies configuration process (no need to write
JSON.stringify('YOUR_FLAG_VALUE')
any more) - it extends flags configuration with modes. It's easy to switch
DEV
/PROD
modes without changing/duplicating configs.
Simply install plugin using npm:
npm install webpack-feature-flags-plugin
or, if you use yarn:
yarn add webpack-feature-flags-plugin
- Create a config file (all the FeatureFlags definitions goes here).
webpack.feature-flags.config.js:
TBD
- Import both plugin and it's config in webpack.config.js:
const FeatureFlagsPlugin = require('webpack-feature-flags-plugin');
const flagsConfig = require('./webpack.feature-flags.config');
- Use imported plugin and config in
plugins
section within webpack.config.js:
...
plugins: [
...
new FeatureFlagsPlugin(flagsConfig, { mode: 'DEV' }), // (flagsConfig, pluginConfig)
...
],
...
- Now you can simply use variables from config in your code.
if (FeatureFlags.featureGroup1.feature1.enabled) {
...
}
Plugin's Constructor accepts two parameters:
-
flagsConfig
- Object, required. Represents your FeatureFlags definitions. -
pluginConfig
- Object, optional. Definesmodes
set and a selectedmode
.modes: ['MODE1', 'MODE2']
- array of modes you want to have to be resolved. Predefined modes arePROD
andDEV
.mode: 'DEV'
- selectedmode
.
There are two possible configuration for FeatureFlags:
- Boolean:
true/false
- plugin does nothing for this case. - String: i.e.
DEV
- in this case plugin checks if this mode is defined inmodes
and does an evaluation.
The evalutation algorithm is pretty simple: if modes are equal - feature flag is set to true
. In all other cases (unknow mode, another mode) - false
.
module.exports =
FeatureFlags: {
namespace1: {
feature10: {
enabled: 'DEV', // FeatureFlags.namespace1.feature10.enabled === true only when plugin configured in 'DEV' mode
},
feature11: {
enabled: true, // FeatureFlags.namespace1.feature11.enabled === true always
}
},
nameSpace2: {
feature20: {
enabled: 'PROD', // FeatureFlags.namespace2.feature20.enabled === true only when plugin configured in 'PROD' mode
},
feature21: {
enabled: false, // FeatureFlags.namespace2.feature21.enabled === true never
},
},
nameSpace3: {
feature30: {
enabled: 'USER_EXTENDED_MODE', // FeatureFlags.namespace3.feature30.enabled === true only when plugin configured in 'USER_EXTENDED_MODE' mode and modes were extended with 'USER_EXTENDED_MODE'
}
}
}
}
Webpack removes dead code using tree-shaking:
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e.
import
andexport
.
Note: It's strongly recommended to read Caveats section to avoid unexpecrted behavior.
Note 2: Using if (FeatureFlags.featureGroup1.feature1.enabled) { ... }
allows webpack to remove dead code, but using the HOC described below doesn't.
withFeatureFlag.js:
import React from 'react';
const withFeatureFlag = (flagName, ComposedComponent, FallbackComponent) => {
class FeatureFlagHOC extends React.PureComponent {
render() {
const isEnabled = FeatureFlags[flagName].enabled;
if (isEnabled) {
return <ComposedComponent {...this.props} />;
}
if (FallbackComponent) {
return <FallbackComponent {...this.props} />;
}
return null;
}
}
return FeatureFlagHOC;
};
export { withFeatureFlag };