When we start building a modern web application normally we don’t really care about scaling that much. This includes all the different environments we are going to install this very app in the future. We want to get shit done and start playing around with our fancy codebase.

So we probably start by doing the very minimum which is to differentiate our application’s setup across three major environments:

development: where we develop new features production: when we build a bundle and we ship it in a production server so that our clients can play around with our cool application test: where we run unit tests locally or in a Continuous Integration system

I know that some of you might wonder if it is too early about that production setup. To be honest it is not since you will probably need to setup a staging server right from the very start so other team members can track your progress and check regularly how this brand new project really looks like. This is where a production-like bundle with lots of optimizations applied needs to be used right?

So obviously we will use Webpack to organize our codebase and establish these 3 major environments setups. My typical webpack setup has a dedicated config file per environment using a self-explanatory name like webpack.development.config.js etc.

In order to avoid repeating myself, i like creating a webpack.base.config.js file first in order to put there some common settings. Then i extend this by using webpack-merge package. All these config files are placed inside a separate folder next to app folder named webpack. How unique right?

Ok let’s see how a simple webpack.base.config.js for a ReactJS app really looks like:

…and this is how i extend it in my webpack.development.config.js:

Since we put some basic pieces in place lets create our npm scripts and implement our webpack config files over there:

Pretty basic stuff above. We have one script to bootstrap our webpack-dev-server in development environment, another one to build our bundle for production environment and last but not least a script to run some unit tests with Jest. Setting up Jest is out of scope for this post but this is a typical script to bootstrap it and run your unit-tests suite.

Ok so we ‘ve made some great progress here but we haven’t actually passed any variable regarding the environment each script runs. The plan is simple. In order to keep things clean we will define the environment variables in webpack.base.config.js file at compile time through webpack.DefinePlugin and the rest config files will just inherit them since they extend this base config file. But how can we pass different variables for each npm script?

The simplest way to do so is to use a super-nifty package named cross-env (which saves us from potential issues caused by Windows OS) and declare the environment variable we need right before running each script.

So what is the first and most important variable we need to specify? You are totally right, time to declare NODE_ENV before each script runs:

We did it but is webpack aware yet? Hmm, no. Remember what we said above. We need to fix our webpack.base.config.js and take advantage of DefinePlugin:

Now webpack knows exactly what goes on regarding the environment we are for each case and uses NODE_ENV especially in production build to optimize our bundle.

The process.env in NodeJS is an object, containing super-useful info regarding user’s environment. So as you see cross-env passes to it the right NODE_ENV value for each case and webpack reads this value from it. If you wonder why we need JSON.stringify() the reason is that webpack does direct text replacement in there so we need to pass a text value with quotes and JSON.stringify() does the job pretty well if you feel lazy like i do :-)

Ok now that we passed NODE_ENV to our scripts through cross-env we can make them a little shorter. If you think about it, we don’t really need the full name of config files. We can create an index.js file in our webpack folder and do sth like this:

…and change our npm scripts accordingly:

Notice that we ask for a config file directly from ./webpack folder without declaring the full path each time.

Perfect, we managed to make all this quite dynamic and we can focus on real development now.

Some months go by and we start having some extra requirements based on clients environment and so on. This very app will be installed in a couple of clients intranets and each of these has some dedicated needs. Each installation will consume an API using a different IP per client. Hmm we need to pass this IP in our webpack bundle at compile time. But how can we do this? Hmm, it is about time to use even more environment variables in our codebase so .env file comes into discussion.

.env is a file where we declare some environment-specific settings in key value pairs like so KEY=VALUE . This file should definitely be ignored by git system to avoid unwanted overrides.

Let’s see an example by solving that issue with API we have:

Using a specific API_URL per environment we install our application is very common and .env is the right place to put that kind of settings.

Since we created our .env file it is time to find a way to read its content and pass it to webpack. To achieve this we will use a pretty-cool package named env-cmd in a similar way as we did with cross-env. Let’s update our package.json accordingly and see how it goes:

…and then update webpack.base.config.js:

Coool. Now we can use this environment-specific API_URL variable in a typical axios setup as shown below:

That’s it. We can add as many environment-specific variables as we like in there as long as we update webpack.base.config.js each time accordingly.

For test environment we kept things pretty simple by using webpack.development.config.js. Feel free to create a dedicated config file to match your needs some more in case you really need so. As you see this pattern is really scalable. Cheers!!