Two practical Webpack performance optimisation
If you have seen my previous blog on Parcel bundler then you'd know my thoughts on Webpack. It is big, complicated and slow. It's big and complex because there are a lot of plugins for it and many ways to set it up as well as customising it. It is slow often due to the enterprise applications it is used in, simply because there are a lot of things going on in these projects than someone's SPA (single page application) personal project. Nobody wants to wait more than 1 min for the build process, here I'll be talking about a couple of practical hacks to boost the Webpack build performance used in a recent project.
Parallelise When Possible
The project is a bit obscure, instead of building and bundling the application into one single minified JS file. Due to the number of region and device supported, we need to build multiple times to satisfy these requirements.
Here is a simple example of the output (we are ignoring device for now for simplicity):
.
+-- index.html
+-- some-other-file.xx
+-- region1
| +-- bundle.js
| +-- some-other-file.xx
+-- region2
| +-- bundle.js
| +-- some-other-file.xx
+-- region3
| +-- bundle.js
| +-- some-other-file.xx
There's a loop that returns an array of configs which Webpack will take and start building all of them sequentially. This is bad, and we need to parallelise this!
If you are not too sure about parallel computing. Imagine someone drops 100 pens on the floor, two people will always pick up these pens faster than one person (assume everyone picks up pens at the same rate). So the more people we can add to this job, the faster it will get done (until you hit the parallel slowdown phenomenon, but that's for another time).
There is a tool called parallel-webpack which accepts the same input as Webpack but runs them in parallel utilising all the CPU threads. Since the input is basically the same, as long as you are providing an array of Webpack configs it will work out of the box with only the following changes:
// instead of doing
webpack --config=webpack.config.dev.js
// just change it to
parallel-webpack --config=webpack.config.dev.js
This alone took our build time from more than 65 seconds to around 35 seconds which is about 46% performance uplift!
If you want to go a step further, according to the parallel-webpack official documentation, turning off statistics also improves performance (version 1.3.0+).
// just change it to
parallel-webpack --no-stats --config=webpack.config.dev.js
Loader Caching
babel-loader caches its results in node_modules/.cache/babel-loader by default. After enabling caching with babel-loader we improved the build performance by another 26%. Although configurations vary among locales, most of the modules are the same. By enabling caching, expensive babel recompilations are avoided for subsequent locales being processed.
I read the above from this blog that claims by caching Babel they were able to achieve a considerable performance gain. I did some simple benchmark tests and here are my results:
Type of build | 1st test | 2nd test | 3rd test | 4th test | Average |
---|---|---|---|---|---|
Without cache build times | 35.295 sec | 30.551 sec | 32.12 sec | 30.867 sec | 32.208 sec |
With cache build times | 26.944 sec | 21.766 sec | 25.93 sec | 20.175 sec | 23.703 sec |
The overall performance increase was 26.3%, the blog above was spot on!
Note: In this project, we only use babel-loader
, but there is no reason why this technique cannot be applied to other loaders.
Final words
This was just me scratching the surface of Webpack optimisation, I'm sure there are hundreds of other things I could be doing to reduce the build time further. But since now on average the build time took less than 30 seconds, it wasn't worth me spending more time tweaking the setup further.
For anyone who's Webpack build time is frustratingly slow, I'd recommend them trying out these two hacks. If these don't apply then be sure to checkout Webpack's own build performance documentation.