Minifying the Build

From the blog:
ajv - The Fastest JSON Schema Validator - Interview with Evgeny Poberezkin

So far, we haven't given thought to our build output and no doubt it's going to be a little chunky, especially as we included React in it. We can apply a variety of techniques to bring down the size of the vendor bundle. We can also leverage client level caching and load certain assets lazily as we saw earlier.

Minification is a process where code is simplified without losing any meaning that matters to the interpreter. As a result, your code will most likely look quite jumbled and it will be hard to read. But that's the point.

Even if we minify our build, we can still generate sourcemaps through the devtool option we discussed earlier. This will give us better means to debug, even production code if we want.

Generating a Baseline Build#

To get started, we should generate a baseline build so we have something to optimize. Execute npm run build. You should end up with something like this:

Hash: 68893abb76c6fcca56cc
Version: webpack 2.2.0
Time: 2016ms
        Asset       Size  Chunks             Chunk Names
         0.js  313 bytes       0  [emitted]
       app.js    2.34 kB       1  [emitted]  app
    vendor.js     141 kB       2  [emitted]  vendor
      app.css     2.2 kB       1  [emitted]  app
     0.js.map  233 bytes       0  [emitted]
   app.js.map    2.58 kB       1  [emitted]  app
  app.css.map   84 bytes       1  [emitted]  app
vendor.js.map     167 kB       2  [emitted]  vendor
   index.html  316 bytes          [emitted]
   [0] ./~/process/browser.js 5.3 kB {2} [built]
   [3] ./~/react/lib/ReactElement.js 11.2 kB {2} [built]
   [7] ./~/react/react.js 56 bytes {2} [built]
...

141 kB for a vendor bundle is a lot! Minification should bring down the size quite a bit.

Minifying the Code#

Ideally, minification will convert our code into a smaller format without losing any meaning. Usually this means some amount of rewriting code through predefined transformations. Good examples of this include renaming variables or even removing entire blocks of code based on the fact that they are unreachable like an if (false) statement.

Sometimes minification can break code as it can rewrite pieces of code you inadvertently depend upon. Angular 1 was an example of this as it relied on a specific function parameter naming and rewriting the parameters could break code unless you took precautions against it.

The easiest way to enable minification in webpack is to call webpack -p. -p is a shortcut for --optimize-minimize, you can think it as -p for "production". Alternately, we can use a plugin directly as this provides us more control.

Setting Up Minification#

As earlier, we can define a little function for this purpose and then point to it from our main configuration. By default, UglifyJS will output a lot of warnings and they don't provide value in this case, so we'll be disabling them in our setup. Here's the basic idea:

webpack.parts.js

...

exports.minifyJavaScript = function({ useSourceMap }) { return { plugins: [ new webpack.optimize.UglifyJsPlugin({ sourceMap: useSourceMap, compress: { warnings: false, }, }) ] }; };

Now we can hook it up with our configuration:

webpack.config.js

...

module.exports = function(env) {
  if (env === 'production') {
    return merge([
      common,
      {
        output: {
          // Tweak this to match your GitHub project name
          publicPath: '/webpack-demo/',
        },
      },
      parts.clean(PATHS.build),
      parts.loadJavaScript(PATHS.app),
parts.minifyJavaScript({ useSourceMap: true }),
... ]); } ... };

If you execute npm run build now, you should see smaller results:

Hash: 68893abb76c6fcca56cc
Version: webpack 2.2.0
Time: 2553ms
        Asset       Size  Chunks             Chunk Names
         0.js  160 bytes       0  [emitted]
       app.js  557 bytes       1  [emitted]  app
    vendor.js    41.9 kB       2  [emitted]  vendor
      app.css     2.2 kB       1  [emitted]  app
     0.js.map  769 bytes       0  [emitted]
   app.js.map     4.8 kB       1  [emitted]  app
  app.css.map   84 bytes       1  [emitted]  app
vendor.js.map     343 kB       2  [emitted]  vendor
   index.html  316 bytes          [emitted]
   [0] ./~/process/browser.js 5.3 kB {2} [built]
   [3] ./~/react/lib/ReactElement.js 11.2 kB {2} [built]
   [7] ./~/react/react.js 56 bytes {2} [built]
...

Given it needs to do more work, it took longer to execute the build. But on the plus side the build is significantly smaller now and our vendor build went from 141 kB to roughly 42 kB.

UglifyJS warnings can help you to understand how it processes the code. Therefore, it may be beneficial to have a peek at the full output every once in a while.

Tree Shaking#

Webpack supports a feature enabled by the ES6 module definition known as tree shaking. The idea is that given it is possible to analyze the module definition in a static way without running it, webpack can tell which parts of the code are being used and which are not. It is possible to verify this behavior by expanding the application a little and adding code there that should be eliminated.

Adjust the component as follows:

app/component.js

export default function () {
const component = function () {
...
}
}; const treeShakingDemo = function () { return 'this should get shaken out'; }; export { component, treeShakingDemo, };

The application entry point needs a slight change as well given the module definition changed:

app/index.js

import 'react';
import 'purecss';
import './main.css';
import component from './component';
import { component } from './component';
...

If you build the project again (npm run build), the vendor bundle should remain exactly the same while the application bundle changes due to the different kind of import. Webpack should pick up the unused code and shake it out of the project.

The same idea works with dependencies that use the ES6 module definition. Given the related packaging standards are still emerging, it is possible you may have to be careful when consuming such packages. Webpack will try to resolve package.json module field for this purpose. See the Consuming Packages chapter for related techniques.

If you want to see which parts of the code tree shaking affects, enable warnings at the UglifyJsPlugin. In addition to other messages, you should see lines like Dropping unused variable treeShakingDemo [./app/component.js:17,6].

Controlling UglifyJS through Webpack#

An UglifyJS feature known as mangling will be enabled by default. The feature will reduce local function and variable names to a minimum, usually to a single character. It can also rewrite properties to a more compact format if configured specifically.

Given these transformations can break your code, you must be a little careful. A good example of this is Angular 1 and its dependency injection system. As it relies on strings, you must be careful not to mangle those or else it will fail to work.

Beyond mangling, it is possible to control all other UglifyJS features through webpack as illustrated below:

new webpack.optimize.UglifyJsPlugin({
  // Don't beautify output (enable for neater output)
  beautify: false,

  // Eliminate comments
  comments: false,

  // Compression specific options
  compress: {
    warnings: false,

    // Drop `console` statements
    drop_console: true
  },

  // Mangling specific options
  mangle: {
    // Don't mangle $
    except: ['$'],

    // Don't care about IE8
    screw_ie8 : true,

    // Don't mangle function names
    keep_fnames: true,
  }
});

If you enable mangling, it is a good idea to set except: ['webpackJsonp'] to avoid mangling the webpack runtime.

Dropping the console statements can be achieved through Babel too by using the babel-plugin-remove-console plugin. Babel is discussed in greater detail in the Processing with Babel chapter.

Other Solutions#

Yet another way to control UglifyJS would be to use the uglify-loader. That gives yet another way to control minification behavior. webpack-parallel-uglify-plugin allows you to parallelize the minifying step and may yield extra performance as webpack doesn't run in parallel by default.

I've listed a couple of UglifyJS alternatives below:

Minifying CSS#

css-loader allows minifying CSS through cssnano. Minification needs to be enabled explicitly using the minimize option. You can also pass cssnano specific options to the query to customize the behavior further.

In webpack 1 minimize was set on by default if UglifyJsPlugin was used. This confusing behavior was fixed in webpack 2 and now you have explicit control over minification.

Conclusion#

Minification is the simplest step you can take to make your build smaller. However, there are a few more tricks we can perform.

This book is available through Leanpub. By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of Webpack.

Need help?