Minifying

a-plus-forms - A+ forms. Would use again - Interview with Nikolay Nemshilov

The build output hasn't received attention yet and no doubt it's going to be chunky, especially as you included React in it. You can apply a variety of techniques to bring down the size of the vendor bundle. You can also leverage client level caching and load individual assets lazily as you saw earlier.

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

Generating a Baseline Build#

To get started, you should generate a baseline build, so you have something to optimize. Execute npm run build to see output below:

Hash: 9164f800e257bf1d9791
Version: webpack 3.8.1
Time: 2398ms
        Asset       Size  Chunks                    Chunk Names
vendor.js 83.7 kB 2 [emitted] vendor
app.js 2.49 kB 1 [emitted] app ... index.html 274 bytes [emitted] [6] ./app/index.js 176 bytes {1} [built] [14] ./app/main.css 41 bytes {1} [built] [15] ./app/component.js 464 bytes {1} [built] ...

83 kB for a vendor bundle is a lot! Minification should bring the size down.

Enabling a Performance Budget#

Webpack allows you to define a performance budget. The idea is that it gives your build size constraint which it has to follow. The feature is disabled by default and the calculation includes extracted chunks to entry calculation. If a budget isn't met and it has been configured to emit an error, it would terminate the entire build.

To integrate the feature into the project, adjust the configuration:

webpack.config.js

const productionConfig = merge([
{ performance: { hints: "warning", // "error" or false are valid too maxEntrypointSize: 50000, // in bytes, default 250k maxAssetSize: 450000, // in bytes }, },
... ]);

In practice you want to maintain lower limits. The current ones are enough for this demonstration. If you build now (npm run build), you should see a warning:

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (50 kB). This can impact web performance.
Entrypoints:
  app (89.5 kB)
      vendor.js
      app.js
      app.css

If minification works, the warning should disappear. That's the next challenge.

Minifying JavaScript#

The point of minification is to convert the code into a smaller form. Safe transformations do this without losing any meaning by rewriting code. Good examples of this include renaming variables or even removing entire blocks of code based on the fact that they are unreachable (if (false)).

Unsafe transformations can break code as they can lose something implicit the underlying code relies upon. For example, Angular 1 expects specific function parameter naming when using modules. Rewriting the parameters breaks code unless you take precautions against it in this case.

Minification in webpack can be enabled through webpack -p (same as --optimize-minimize). This uses webpack's UglifyJsPlugin underneath.

Setting Up JavaScript Minification#

uglifyjs-webpack-plugin allows you to use ES2015 syntax out of the box and minify it.

To get started, include the plugin to the project:

npm install uglifyjs-webpack-plugin --save-dev

To attach it to the configuration, define a part for it first:

webpack.parts.js

...
const UglifyWebpackPlugin = require("uglifyjs-webpack-plugin");

exports.minifyJavaScript = () => ({
  plugins: [new UglifyWebpackPlugin()],
});

The plugin exposes more functionality, but having the possibility of toggling source maps is enough. Hook it up with the configuration:

webpack.config.js

const productionConfig = merge([
  ...
  parts.clean(PATHS.build),
parts.minifyJavaScript(),
... ]);

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

Hash: 9164f800e257bf1d9791
Version: webpack 3.8.1
Time: 3165ms
        Asset       Size  Chunks             Chunk Names
vendor.js 28.1 kB 2 [emitted] vendor
app.js 675 bytes 1 [emitted] app ... app.css.map 84 bytes 1 [emitted] app
vendor.js.map 86 bytes 2 [emitted] vendor
index.html 274 bytes [emitted] [6] ./app/index.js 176 bytes {1} [built] [15] ./app/main.css 41 bytes {1} [built] [16] ./app/component.js 464 bytes {1} [built] ...

Given it needs to do more work, it took longer to execute the build. But on the plus side, the build is now smaller, the size limit warning disappeared, and the vendor build went from 83 kB to roughly 28 kB.

Source maps are disabled by default. You can enable them through the sourceMap flag. You should check uglifyjs-webpack-plugin for more options.

Other Ways to Minify JavaScript#

Although uglifyjs-webpack-plugin works for this use case, there are more options you can consider:

Speeding Up JavaScript Execution#

Certain solutions allow you to preprocess code so that it will run faster. They complement the minification technique and can be split into scope hoisting, pre-evaluation, and improving parsing. It's possible these techniques grow overall bundle size sometimes while allowing faster execution.

Scope Hoisting#

webpack.optimize.ModuleConcatenationPlugin hoists all modules to a single scope instead of writing a separate closure by each. Doing this slows down the build but gives you bundles that are faster to execute. Read more about scope hoisting at webpack blog.

Pass --display-optimization-bailout flag to webpack to gain debugging information related to hoisting results.

Pre-evaluation#

prepack-webpack-plugin uses Prepack, a partial JavaScript evaluator. It rewrites computations that can be done compile-time and therefore speeds up code execution. See also val-loader and babel-plugin-preval for alternative solutions.

Improving Parsing#

optimize-js-plugin complements the other solutions by wrapping eager functions and it enhances the way your JavaScript code gets parsed initially. The plugin relies on optimize-js by Nolan Lawson.

Minifying HTML#

If you consume HTML templates through your code using html-loader, you can preprocess it through posthtml with posthtml-loader. You can use posthtml-minifier to minify your HTML through it.

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.

clean-css-loader allows you to use a popular CSS minifier clean-css.

optimize-css-assets-webpack-plugin is a plugin based option that applies a chosen minifier on CSS assets. Using ExtractTextPlugin can lead to duplicated CSS given it only merges text chunks. OptimizeCSSAssetsPlugin avoids this problem by operating on the generated result and thus can lead to a better result.

Setting Up CSS Minification#

Out of the available solutions, OptimizeCSSAssetsPlugin composes the best. To attach it to the setup, install it and cssnano first:

npm install optimize-css-assets-webpack-plugin cssnano --save-dev

Like for JavaScript, you can wrap the idea in a configuration part:

webpack.parts.js

...
const OptimizeCSSAssetsPlugin = require(
  "optimize-css-assets-webpack-plugin"
);
const cssnano = require("cssnano");

exports.minifyCSS = ({ options }) => ({
  plugins: [
    new OptimizeCSSAssetsPlugin({
      cssProcessor: cssnano,
      cssProcessorOptions: options,
      canPrint: false,
    }),
  ],
});
If you use --json output with webpack as discussed in the Build Analysis chapter, you should set canPrint: false for the plugin.

Then, connect with main configuration:

webpack.config.js

const productionConfig = merge([
  ...
  parts.minifyJavaScript(),
parts.minifyCSS({ options: { discardComments: { removeAll: true, }, // Run cssnano in safe mode to avoid // potentially unsafe transformations. safe: true, }, }),
... ]);

If you build the project now (npm run build), you should notice that CSS has become smaller as it's missing comments:

Hash: 9164f800e257bf1d9791
Version: webpack 3.8.1
Time: 3254ms
        Asset       Size  Chunks             Chunk Names
...
  ...font.ttf     166 kB          [emitted]
app.css 2.25 kB 1 [emitted] app
0.js.map 2.07 kB 0 [emitted] app.js.map 1.64 kB 1 [emitted] app ...

Minifying Images#

Image size can be reduced by using img-loader, imagemin-webpack, and imagemin-webpack-plugin. The packages use image optimizers underneath.

It can be a good idea to use cache-loader and thread-loader with these as discussed in the Performance chapter given they can be heavy operations.

Conclusion#

Minification is the easiest step you can take to make your build smaller. To recap:

  • Minification process analyzes your source code and turns it into a smaller form with the same meaning if you use safe transformations. Certain unsafe transformations allow you to reach even smaller results while potentially breaking code that relies, for example, on exact parameter naming.
  • Performance budget allows you to set limits to the build size. Maintaining a budget can keep developers more conscious of the size of the generated bundles.
  • Webpack includes UglifyJsPlugin for minification. Other solutions, such as babel-minify-webpack-plugin, provide similar functionality with costs of their own.
  • Besides JavaScript, it's possible to minify other assets, such as CSS, HTML, and images, too. Minifying these requires specific technologies that have to be applied through loaders and plugins of their own.

You'll learn to apply tree shaking against code in the next chapter.

Previous chapterOptimizing
Next chapterTree Shaking

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). 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?