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 @include
s 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.