How to speed up webpack with esbuild-loader

If you are a webpack user and have heard about esbuild speed, you may start questioning your js-bundler choices. Luckily, you don't have to drop your hand-crafted webpack config just now. Thanks to esbuild-loader, you can get part of the speed improvement without doing a whole migration.

Benchmark repo

For checking the esbuild-loader with a non-trivial amount of code, I created a repository. My code is minimal, src/index.js:

import { PDFDocument } from "pdf-lib";

const pdfButton = document.getElementById("pdf-button");

pdfButton.addEventListener("click", async () => {
  const pdfDoc = await PDFDocument.create();
  const page = pdfDoc.addPage([350, 400]);
  page.moveTo(110, 200);
  page.drawText("Hello World!");
  const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
  document.getElementById("pdf").src = pdfDataUri;
});

index.html:

<!DOCTYPE html>

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>esbuild-loader pdf-lib</title>
    <link rel="shortcut icon" href="#" />
  </head>
  <body>
    <button id="pdf-button">Generate pdf</button>

    <br />

    <iframe id="pdf" style="width: 350px; height: 600px"></iframe>

    <script type="text/javascript" src="dist/main.js"></script>
  </body>
</html>

and it does mainly 2 things:

  • loads pdf-lib - pure js pdf library, so our build has something to spend time on
  • provides some operation we can execute to see if the built code works as expected

Standard webpack build

This code is meant to be built with the default webpack configuration. If you checkout the webpack branch, the build will go more or less like this:

$ npm run build

> [email protected] build
> webpack --mode=production

asset main.js 413 KiB [compared for emit] [minimized] [big] (name: main) 1 related asset
orphan modules 936 KiB [orphan] 156 modules
runtime modules 663 bytes 3 modules
cacheable modules 1.12 MiB
  modules by path ./node_modules/pako/lib/zlib/*.js 183 KiB 11 modules
  modules by path ./node_modules/pako/lib/utils/*.js 7.56 KiB
    ./node_modules/pako/lib/utils/common.js 2.39 KiB [built] [code generated]
    ./node_modules/pako/lib/utils/strings.js 5.17 KiB [built] [code generated]
  modules by path ./node_modules/pako/lib/*.js 22.9 KiB
    ./node_modules/pako/lib/deflate.js 10.8 KiB [built] [code generated]
    ./node_modules/pako/lib/inflate.js 12.1 KiB [built] [code generated]
  ./src/index.js + 155 modules 937 KiB [built] [code generated]
  ./node_modules/pako/index.js 347 bytes [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets: 
  main.js (413 KiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (413 KiB)
      main.js


WARNING in webpack performance recommendations: 
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.45.1 compiled with 3 warnings in 5983 ms

Key numbers to remember:

  • complication time - almost 6s
  • output size - 413 KiB

esbuild-loader enhanced build

esbuild-loader provides 2 ways of using esbuild in webpack:

  1. as a loader that can replace babel-loader or ts-loader
  2. ESBuildMinifyPlugin that we can use to replace the default terser plugin

I haven't seen any improvements in my example repo by using the loader in my example repo - it even got a bit slower. Luckily, the minification improvements are interesting enough to recommend you to give it a try. You need webpack.config.js with:

const { ESBuildMinifyPlugin } = require("esbuild-loader");

module.exports = {
  mode: "production",
  optimization: {
    minimizer: [
      new ESBuildMinifyPlugin({
        target: "es2015", // Syntax to compile to (see options below for possible values)
      }),
    ],
  },
};

install esbuild-loader with:

$ npm install --save-dev esbuild-loader

and your build will look like this:

$ npm run build          

> [email protected] build
> webpack --mode=production

asset main.js 426 KiB [compared for emit] [minimized] [big] (name: main)
orphan modules 936 KiB [orphan] 156 modules
runtime modules 663 bytes 3 modules
cacheable modules 1.12 MiB
  modules by path ./node_modules/pako/lib/zlib/*.js 183 KiB 11 modules
  modules by path ./node_modules/pako/lib/utils/*.js 7.56 KiB
    ./node_modules/pako/lib/utils/common.js 2.39 KiB [built] [code generated]
    ./node_modules/pako/lib/utils/strings.js 5.17 KiB [built] [code generated]
  modules by path ./node_modules/pako/lib/*.js 22.9 KiB
    ./node_modules/pako/lib/deflate.js 10.8 KiB [built] [code generated]
    ./node_modules/pako/lib/inflate.js 12.1 KiB [built] [code generated]
  ./src/index.js + 155 modules 937 KiB [built] [code generated]
  ./node_modules/pako/index.js 347 bytes [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets: 
  main.js (426 KiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (426 KiB)
      main.js


WARNING in webpack performance recommendations: 
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.45.1 compiled with 3 warnings in 1658 ms

Key values:

  • complication time - below 2s
  • output size - 426 KiB

So we got our build time about 3 times lower, with a 3% increase in build size.

Links

Summary

If you are interested in speeding up your build time but have no time for replacing the whole bundler & the setup, ESBuildMinifyPlugin from esbuild-loader can be a good way to achieve that.

28