6 September 2018

Networks
Software Development

Webpack 3 to webpack 4: tips on migrating

6 minutes reading

Webpack 3 to webpack 4: tips on migrating

Having heard a lot about speed improvements, we recently migrated from webpack 3 to webpack 4. The results have been astonishing: our average build time improved by 30%, and hot-reloading by 83%!

This article covers the following topics:

  1. The key benefits of migration
  2. How webpack 3 compares to webpack 4 in terms of build time
  3. How to effectively migrate from webpack 3 to webpack 4
  4. The pitfalls to avoid

I’ll start by saying our project is humongous, so builds are pretty slow. Our hot-update recently got so slow, in fact, that we decided something had to be done.

Here’s a quick comparison of build times before and after the update.

table of webpack

As you can see, the gains are impressive. Previously, hot-update took the same amount of time that a complete restart the dev-server takes now. I’m not saying that the configuration was ideal before, and nor is the current one perfect, either.

Things we got rid of:

  • ExtractTextPlugin
  • optimize.CommonsChunkPlugin (it’s built in now)
  • NamedModulesPlugin (this is also built in)

Things we have added:

  • MiniCssExtractPlugin
  • UglifyJsPlugin (moved to different part in config)
  • HardSourceWebpackPlugin
  • ForkTsCheckerWebpackPlugin
  • Thread-loader
  • Postcss-preset-env
  • Devcert-cli
Improve your network operations. Check how we can help.

So how did we do it?

Anyone who has tried to build a good configuration from the ground up knows that webpack documentation has some missing pieces. Add some plugins and typescript to the mix, and you get a tight-rope walk of sorts - one false move and things start to tumble.

That aside, webpack does maintain backward compatibility, so you can end up using a mix of syntax from each version of webpack in your configuration.

We started by updating npm packages of webpack, its plugins and loaders to the newest versions.This gave us a completely broken build.

We then updated our config to utilize the new mode property introduced in webpack 4. This new property controls default configuration and what default plugins are loaded. It should be sufficient for some test projects without writing your own config at all! It’s a big improvement, especially for people just starting out with this tool.

Be sure to check out the webpack documentation when upgrading to see which plugins are enabled by default: https://webpack.js.org/concepts/mode/

Also keep in mind that some plugins configuration have been moved to the optimizations branch of the configuration. For example, minimizer will be your UglifyJS config and splitChunks will replace CommonsChunkPlugin.

Previously we had to add the NODE_ENV variable in our npm scripts and then, based on that, change the config to match the production or development build. This also introduced another library to our toolchain so we could make sure that the script would work on every environment.

Before:

cross-env NODE_ENV=production webpack

After:

webpack --mode=production

After that we looked at our plugins and loaders.

We next had to remove the extract-text-webpack-plugin, which had been used to extract css from our bundle to a separate file on the production build. This was replaced by a mini-css-extract-plugin that is maintained by the webpack team.

We also changed the awesome-typescript-loader, which was once a lot faster. Ts-loader has made a huge leap forward in terms of performance, so we went back to it.

Threads represent another big improvement in build times. The webpack team maintains a cool package called thread-loader. You just have to add it before any time-consuming loader and it will be split into multiple threads utilizing your multi-core processor! It’s as simple as that.

{
 test: /.tsx?$/,
 include: [APP_PATH],
 exclude: /node_modules/,
 use: [
   {
     loader: 'thread-loader',
     options: {
       // There should be 1 cpu for the fork-ts-checker-webpack-plugin
       workers: CPU_COUNT - 1,
       // Keeps workers alive for dev rebuilding (starting a worker takes some time)
       poolTimeout: isDev(mode) ? Infinity : 2000,
     },
   },
   {
     loader: 'ts-loader',
     options: {
       happyPackMode: true,
       configFile: root('tsconfig.project.json'),
     },
   },
 ],
},

We just need to add fork-ts-checker-plugin, which will delegate type checking to another thread.

An additional flag (checkSyntacticErrors) is required because we disabled all type checking capabilities of the ts-loader by adding happyPackMode flag for compatibility with thread-loader.

new ForkTsCheckerWebpackPlugin({ checkSyntacticErrors: true }),

But how do you get these improvements when restarting the dev-server?And that’s all you need to improve average build time by 30%, and hot-reloading by 83%!

The solution is pretty simple. Use the hard-source-webpack-plugin. It integrates seamlessly with webpack, creating a cache when you first start your dev-server. Every other time you want to start the dev-server, those cached files will be used to skip the build process.

There are two downsides to this approach: First, the first time you are building, it will be a bit slower than without this plugin. The upside here is that other runs will be considerable faster-- 72% faster in our case.

The other downside is that the cache must be stored somewhere, and your SSD will be none too happy about it.

Bonus

Have you ever tried to provide an SSL certificate for your local development setup? Maybe you even got it working, but then Chrome rolled up an update and it was broken again?

Look no more. The solution is super simple - devcert-cli.

npm i devcert-cli --save-dev

Add this to .gitignore

localhost.key
localhost.cert

And add the following to your postinstall script in package.json and you’re good to go.

"postinstall": "node node_modules/devcert-cli/bin/run generate localhost",
Damian

Damian Osipiuk

Team Leader - Senior Frontend Engineer