It is a common occurrence that a product’s web application needs to enable/disable features depending upon the circumstances.
Some use cases include:
- Wanting to merge features for an in-progress project without showing/releasing them to customers in production
- Wanting to preview in-progress work in staging environment
- Pausing features without removing them from source code
Here’s an example:
// Home.jsx function Home() if (!devFlags.beta) return ...; return ...;
There are three things we need to do to make this work well:
- Set a pattern for configuring dev flags locally
- Set a pattern for overriding dev flags in an environment
- Set a pattern for how to consume dev flags in the code
Dev flags are just configuration, so you can think of them as a single object that can be consumed anywhere in the codebase:
export default beta: true, // if true, show the features for the beta launch ;
However, we will need to specify the “defaults” that control the live production experience and the “overrides” that control the local environment.
For example, imagine you want the dev flag object to return
beta: false in production (before its release), but
beta: true locally so that you can develop features before the release.
First, create a
dev-flags-default.js file that controls the live production experience (the default experience):
// dev-flags-defaults.js export default beta: false, // Don't show beta features in production ;
Next, create a
dev-flags-overrides.js file that can specify the overrides fo the local experience:
// dev-flags-overrides.js export default beta: true, // Show beta features as you develop ;
NOTE: You’ll want to add this file to the
.gitignore since the overrides should always be specific to the environment (more on that later).
Finally, expose a
dev-flags.js file (the file that will be consumed by other files in the codebase):
// dev-flags.js import defaults from './dev-flags-defaults.js'; import overrides from './dev-flags-overrides.js'; export ...defaults, ...overrides, ;
Now, you will be able to control what features to render based upon whether you are in a production or local environment.
The code above expects a
dev-flags-overrides.js file in every environment.
Obviously, you can add it manually to each local environment and instruct all the developers to do so via documentation.
However, we’ll have to think about how this will work when the app is deployed to various environments (i.e. staging and production).
In the deployment pipeline, you’ll need a step for adding the
dev-flags-overrides.js file with the overrides appropriate to the environment.
For production, you can create a
dev-flags-overrides.js file that returns an empty object.
For staging, you can copy the defaults and enable them as needed.
Once you have the dev flags patterns set up per environment, you can start writing code that toggles features based on a flag.
Since dev flags frequently control revealing features of an in-progress project, you’ll want to make the
!devFlags.someFlag code easy to cleanup (since it will eventually go away:
// Home.jsx import devFlags from './dev-flags.js'; // Bad function Home() if (devFlags.someFlag) return ...; return ...; // Good function Home() if (!devFlags.someFlag) return ...; return ...;
Then to release a feature, you can cleanup the dev flags by deleting the
!devFlags.flag code (as opposed to copy and pasting the code in the
if block in the bad example).
Sometimes, you may wish to enable a dev flag to release a feature/project as opposed to cleaning it up.
This may be nice so that you can quickly enable the feature, make sure everything is sound, and then delete the flag and all its references in a later commit.
🎉 Now you have an organized pattern for enabling/disabling features by environment. If you don’t have something like this in your codebase, follow the steps above (it’s just a few small files).