How We Achieved a 7x Speed-Up of Our Webpack (TailwindCSS) Build
Paweł Kowalski | April 7, 2021
Since Github Actions became a thing, I became interested in saving precious seconds in our plan by optimizing assets build time. As long as it didn't exceed 60 seconds, there was little motivation, but once it did, I said enough is enough.
Our build step runs two commands:
- npm ci
- npm run build
We use babel and TailwindCSS. Using simple methods, I discovered that CSS and JS take about the same time to build.
Benchmark: Webpack build took 64 seconds, whole build took 91 seconds.
Replacing babel-loader + terser with esbuild loader
The first thing I did was replace babel-loader and Terser (minification tool) with esbuild-loader. This made our JS compile around 12 times faster, it went down to 1.4 seconds. It was a good start.
Result: Webpack build took 40 seconds (down from 64), whole build took 65 seconds.
Configuring Tailwind
The second optimization had to do something with CSS because that's where most of the build time now lay. I visited the TailwindCSS documentation to find out how to decrease build size.
The template we base our projects on uses a style guide with predefined colors, sizes, etc., so we don't need some of the configuration from TailwindCSS. Knowing this, here's what I did:
- Moved colors configuration outside extend, which disabled all the built-in colors from the build. This made a huge difference in development file size (10.5MB → 4MB).
- Moved spacing configuration outside extend
- Disabled corePlugins
I had to bring some of these back because we used them in a couple of places. After these changes, I considered TailwindCSS optimization done.
The file size of development CSS went down from 10.5MB to 3.9MB, which is a big deal (depending on the developer's connection speed) if you are sending your CSS on every CSS change. It does not happen often when working with TailwindCSS, but it was still a welcome improvement for everyone using our pos-cli sync command.
Result: Webpack build took 26 seconds (down from 40), whole build took 49 seconds.
Read about TailwindCSS optimization in their documentation:
Disabling PostCSS processing
The last step was to disable PostCSS processing of CSS pulled in from node_modules.
Before, it was pretty naive — everything went through all the CSS loaders:
{
test: /(\.css)$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { url: false } },
'postcss-loader',
],
},
I split it into two so that node_modules are processed only by css-loader, and application CSS is processed by PostCSS first.
{
test: /(\.css)$/,
include: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { url: false } }
],
},
{
test: /(\.css)$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { url: false } },
'postcss-loader',
],
},
Result: Webpack build took 17 seconds (down from 26), whole build took 39 seconds.
That's all :)
This journey was one of those creative ones where you fiddle around, find gains in various places, and they add up. In this case, they added up to 52 seconds of savings for every build. And we build a lot, so this improves our developer experience and lowers the cost of CI.
Summary
Action | Webpack build time (seconds) | Whole build time (seconds) |
---|---|---|
At the start | 64 | 91 |
Using esbuild loader | 40 | 65 |
Configuring Tailwind | 26 | 49 |
Disabling PostCSS processing | 17 | 39 |
In the following article, I will report how development speedup is going because the TailwindCSS team just released the experimental TailwindCSS JIT, which might improve live development even further.
Update: How we sped up our Webpack (TailwindCSS) build from 64 to 9 seconds
Above, we described how we went from 64 seconds to 17 seconds on our Webpack build (measured on GithubActions, a pretty slow environment CPU-wise). Just as we did it and managed to write an article about it to share the knowledge, something amazing happened: TailwindCSS/JIT.
JIT (short for Just In Time) for TailwindCSS is a much more performant way of generating the TailwindCSS output file. Instead of generating a big (sometimes 10MB+) CSS file and then using PurgeCSS to remove unnecessary classes, it only generates what is needed in the first place. This makes PurgeCSS and many other speed optimization techniques in TailwindCSS unnecessary. It is very fast no matter what config you use, and the output file size is still optimal.
We jumped into experimenting with JIT as soon as it got a beta release, so there were some bugs, but now we consider it good enough for production, hence this update.
Result: Webpack build took 8.9 seconds (down from 17)
As a nice side-effect of using JIT, our TailwindCSS config became much smaller because we don't need to disable modules, override theme colors, spacing, etc. Now everything is taken care of by JIT during runtime. It is so fast that development became a breeze.
Interested in knowing more about partnering with platformOS?
Ensure your project’s success with the power of platformOS.