Setting Environment Variables

React relies on process.env.NODE_ENV based optimizations. If we force it to production, React will get built in an optimized manner. This will disable some checks (e.g., property type checks). Most importantly it will give you a smaller build and improved performance.

The Basic Idea of DefinePlugin

Webpack provides DefinePlugin. It is able to rewrite matching free variables. To understand the idea better, consider the example below:

var foo;

// Not free, not ok to replace
if(foo === 'bar') {
  console.log('bar');
}

// Free, ok to replace
if(bar === 'bar') {
  console.log('bar');
}

If we replaced bar with a string like 'bar', then we would end up with code like this:

var foo;

// Not free, not ok to replace
if(foo === 'bar') {
  console.log('bar');
}

// Free, ok to replace
if('bar' === 'bar') {
  console.log('bar');
}

Further analysis shows that 'bar' === 'bar' equals true so UglifyJS gives us:

var foo;

// Not free, not ok to replace
if(foo === 'bar') {
  console.log('bar');
}

// Free, ok to replace
if(true) {
  console.log('bar');
}

And based on this UglifyJS can eliminate the if statement:

var foo;

// Not free, not ok to replace
if(foo === 'bar') {
  console.log('bar');
}

// Free, ok to replace
console.log('bar');

This is the core idea of DefinePlugin. We can toggle parts of code using it using this kind of mechanism. UglifyJS is able to perform the analysis for us and enable/disable entire portions of it as we prefer.

Setting process.env.NODE_ENV

To show you the idea in practice, we could have a declaration like if(process.env.NODE_ENV === 'development') within our code. Using DefinePlugin we could replace process.env.NODE_ENV with 'development' to make our statement evaluate as true just like above.

As before, we can encapsulate this idea to a function:

libs/parts.js

...

leanpub-start-insert
exports.setFreeVariable = function(key, value) {
  const env = {};
  env[key] = JSON.stringify(value);

  return {
    plugins: [
      new webpack.DefinePlugin(env)
    ]
  };
}
leanpub-end-insert

We can connect this with our configuration like this:

webpack.config.js

...

// Detect how npm is run and branch based on that
switch(process.env.npm_lifecycle_event) {
  case 'build':
    config = merge(
      common,
      {
        devtool: 'source-map'
      },
leanpub-start-insert
      parts.setFreeVariable(
        'process.env.NODE_ENV',
        'production'
      ),
leanpub-end-insert
      parts.minify(),
      parts.setupCSS(PATHS.style)
    );
    break;
  default:
    ...
}

module.exports = validate(config);

Execute npm run build again, and you should see improved results:

[webpack-validator] Config is valid.
Hash: 9880a5782dc874c824c4
Version: webpack 1.13.0
Time: 3004ms
     Asset       Size  Chunks             Chunk Names
    app.js    25.4 kB       0  [emitted]  app
app.js.map     307 kB       0  [emitted]  app
index.html  157 bytes          [emitted]
   [0] ./app/index.js 123 bytes {0} [built]
  [36] ./app/component.js 136 bytes {0} [built]
    + 35 hidden modules
Child html-webpack-plugin for "index.html":
        + 3 hidden modules

So we went from 133 kB to 38 kB, and finally, to 25.4 kB. The final build is a little faster than the previous one. As that 25.4 kB can be served gzipped, it is quite reasonable. gzipping will drop around another 40%. It is well supported by browsers.

T> babel-plugin-transform-inline-environment-variables Babel plugin can be used to achieve the same effect. See the official documentation for details.

T> Note that we are missing react-dom from our build. In practice our React application would be significantly larger unless we are using a lighter version such as preact or react-lite. These libraries might be missing some features, but they are worth knowing about if you use React.

Conclusion

Even though simply setting process.env.NODE_ENV the right way can help a lot especially with React related code, we can do better. We can split app and vendor bundles and add hashes to their filenames to benefit from browser caching. After all, the data that you don't need to fetch loads the fastest.

results matching ""

    No results matching ""