Portfolio Website

Optimizing Webpack

Writing TypeScript on the frontend of an application feels awesome. Type completion's aid cannot be overstated when working with the DOM. Add in npm packages? Sweet.

The only thing that bothered me was how long it took to compile.

Analysis

There are a few tools available to analyze the performance. By default, Webpack will simply tell you the milliseconds elapsed during the process:

webpack 5.99.8 compiled with 19 warnings in 4682 ms

(Ignore the number of warnings; these are from dependencies)

That's hardly useful information. Our Webpack buildtime is effectively a black box at this point. What's taking it so long? Consequently, what steps can I take to speed it up? These questions may be answered by some plugins available via npm.

Speed Measure Plugin

Speed Measure Plugin (speed-measure-webpack-plugin on npm) is a wrapper for the Webpack config file. In your webpack.config.js, you can wrap the entire export with a SpeedMeasurePlugin instance:

import { SpeedMeasurePlugin } from "speed-measure-webpack-plugin";

const smp = new SpeedMeasurePlugin();

export default smp.wrap({
    // Webpack config
});

When running webpack, SMP gives you a solid stdout breakdown of which loaders and plugins are taking the most time:

 SMP  ⏱
General output time took 5.82 secs

 SMP  ⏱  Plugins
EsbuildPlugin took 0.545 secs

 SMP  ⏱  Loaders
css-loader, and
sass-loader took 3.48 secs
  module count = 96
modules with no loaders took 1.9 secs
  module count = 999
style-loader, and
css-loader, and
sass-loader took 0.118 secs
  module count = 96
esbuild-loader took 0.034 secs
  module count = 2

How long SMP took for the overall compilation was at some points significantly longer than the sum of loaders and plugins, though. Additionally, there was a known bug on GitHub Issues that's gone unfixed for years. Onto the next plugin.

Rsdoctor

A recent comment on that GitHub issue suggested using Rsdoctor instead. After taking a look, I couldn't be more happy.

Rsdoctor (@rsdoctor/webpack-plugin on npm) is a Webpack analysis plugin that doesn't need to wrap around your config export. Instead, Rsdoctor drops into your Webpack plugin list:

import { RsdoctorWebpackPlugin } from "@rsdoctor/webpack-plugin";

export default {
    plugins: [new RsdoctorWebpackPlugin()]
};

When running webpack with this extension, there is no stdout information about the bundling itself. Instead, it gives you a link to a webserver that provides this information:

[Rsdoctor info] RsdoctorWebpackPlugin compiler's analyzer \
   running on: http://192.168.1.10:4105/index.html

Rsdoctor provides detailed metrics on the bundle size, compilation times, duplicate chunks, and more in an aesthetically pleasing web interface. Thanks to this tool, the bottlenecks were quickly apparent.

Problems & Solutions

First and foremost, sass-loader takes the most time by a mile. It doesn't seem like this can be much improved. Optimizing Bootstrap by removing unused component @includes does hardly anything.

Next up, ts-loader and terser, responsible for building and minifying the TypeScript files, were slower than I'd like. Fixing this was easy. I've added esbuild in place of both these in the config. Because it was written and compiled with Go, esbuild is multitudes faster than the others. The project is massively popular, so extensibility and support are no issue, either.

I've also set esbuild to not build source-maps for production.

Outcome

It used to take ~17 seconds to do a clean production build. It now takes ~5. That's a good difference.

Keep checking in for more insights on software architecture and design.