Optimizing Javascript with Environment Variables

In this article we're going to turn our project into something that works in both production and development, using environment variables with Webpack. We'll also discuss how libraries like React use these variables to make smaller bundles in production.

We're going to start where we let off in production ready stylesheets. If you need to catch up:

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout prod-js
npm install

Entry Arrays are our Friend

We've got some commented javascript in our main.js so we'll move that to the config files. Webpack is a lot simpler than it seems. The entry takes an array that can be thought of in the same way as a list of requires in any javascript file. Webpack will string these together like it does with anything else. So in config/webpack.dev.js, in our main entry, we're going to add the development only code before our main.js and then, remove them from the main.js, which is also used in production.

entry: {
  main: [
    "babel-runtime/regenerator",
    "webpack-hot-middleware/client?reload=true",
    "./src/main.js"
  ]
}

Environment Variables

Our project doesn't have a lot of javascript in it yet. So any optimization we do will feel a little underwhelming, but as we add to it, it'll get more important and we'll continue to optimize for production throughout the course. First things first, lets let define our production environment.

In the plugins array in webpack.prod.js:

new webpack.DefinePlugin({
  "process.env": {
    NODE_ENV: JSON.stringify("production")
  }
})

This will add a variable to all of our code during the webpack compilation.

In main.js at the bottom, lets add:

console.log(`Environment is ${process.env.NODE_ENV}`)

Now if we build then run the production version of this site.

npm run build
heroku local

Console log results in production

We have the environment variable logged to the console and we can use that in our Javascript.

if (process.env.NODE_ENV !== "production")
  Error.new("This is some non-production error")

React, for instance uses this very idea to cut out a lot of development only magic and really get that file size down. Let's add React.

npm install react

And add it to our src/main.js

require("react")

We see that running npm run build which uses webpack.prod.js in all 3 cases, without React is 3.25 kB. If we change this variable to:

'NODE_ENV': JSON.stringify('development')

Or take it out completely, React doesn't know it's in production mode, so it outputs a 70 kB file. We still run npm run build which, if we check, still sets NODE_ENV=production from the command line.

"build": "NODE_ENV=production webpack --config=config/webpack.prod.js",

Comparative sizes with and without

So the final savings is 70 - 14.8 - 3.25 or 51.95 kB, and it'll get even smaller in future articles with Uglify, Preact and Compression, which we'll get to in later articles.

Webpack's own Environment

This is a pretty cool feature of webpack that not a lot of folks use. When defining a config, like we do in webpack.prod.js, we can export a function instead of an object.

module.exports = env => {
  return {
    entry: {
      main: ["./src/main.js"]
    },
    ...
  }
}

Relatively minor change. The env variable is the argument and the config object is returned from the function.

In the DefinePlugin we can now use the env.NODE_ENV variable instead of a hard coded string.

new webpack.DefinePlugin({
  "process.env": {
    NODE_ENV: JSON.stringify(env.NODE_ENV)
  }
}),

Now let's change the syntax of our build to use webpack's environment variable.

"build":
  "webpack --config=config/webpack.prod.js --env.NODE_ENV=production ",

Of course, you need not use this only for NODE_ENV. You could pass any variable you wanted in the command line and access it within the config.

This pattern of assembling configs is common in webpack boilerplates out there that don't use separate dev and prod config files, so it's a good thing to know.

I find keeping config files simple is most important so I don't tend to use this feature.

Also, I don't know why Webpack doesn't just use the shell environment variables and requires this command line argument. If you know, let me know.

In Sum

In this article we covered the various ways environment variables are used in Webpack configs. We showed the gains in production builds with 3rd party libraries like React and the flexibility with which we can add variables to the compiler at run time.

Up Next

In the next article we'll finish optimizing our Javascript with a look at how Uglify, ClosureCompiler and other forms of compression work to make our javascript bundles truly small.