How to speed up Angular CLI app with esbuild-loader
Tags: angular, esbuild
Published: 2021-07-18 10:01:11

A previews article in this series showed [how to build Angular CLI app with esbuild](https://how-to.dev/how-to-build-angular-cli-application-with-esbuild). There are two downsides of this approach:
* we drop every piece of configuration that is there in the Angular CLI build configuration
* our build **is** faster, but it's way bigger - ~760kb instead of ~170kb

This article will try a different approach to harvest esbuild speed for your Angular CLI application. As I already shown in another post, [using esbuild-loader provided minimization plugin](https://how-to.dev/how-to-speed-up-webpack-with-esbuild-loader) can be pretty effective.

# A word of warning
This is not an easy configuration. Getting it up & running made my head spin, and I started questioning my framework and tooling choices along the way. In the end, I got something working, but the results are not impressive. It probably deserves a heavier test, similar to the benchmark used in the post linked above.

# Overriding webpack configuration in Angular CLI

Angular CLI used to have `ng eject` command to expose the webpack configuration. Unfortunately, since version 8, it's not available anymore. Instead, we can specify "projects.*.architect.build.builder" inside our `angular.js` to override the webpack config partially - without getting access to the whole thing.

This is the approach used in [angular-builders project](https://angular-builders.dev/home), which I used in this guide.

# The approach

Here, I'll set up only `ESBuildMinifyPlugin` from esbuild-loader. The in the webpack article, it looked like better speed improvement than the loader itself. As angular is written in typescript, the loader can improve speed, but I'm afraid it will be an even bigger endeavor than this one.

# Setting up upgraded configuration

For a starter, let's install the necessary dependencies:
```sh
$ npm install --save-dev @angular-builders/custom-webpack esbuild-loader
```

The configuration change can be tricky. First, let's add our `webpack.config.js` with changes we want to apply:
```js
const { ESBuildMinifyPlugin } = require("esbuild-loader");

module.exports = (config, options) => {
  // if you console log here, you can see what's inside the config

  // remove 2 first minimizers, hopeing they are the TerserPlugin
  config.optimization.minimizer.shift();
  config.optimization.minimizer.shift();

  config.optimization.minimizer.unshift(
    new ESBuildMinifyPlugin({
      target: "es2015", // Syntax to compile to (see options below for possible values)
    })
  );

  return config;
};
```

To make it work, let's update `angular.json`:
```JSON
  "projects": {
    "esbuild-loader-ng-cli": {
       ...
       "architect": {
         "build": {
-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular-builders/custom-webpack:browser",
           ...
-            "baseHref": "./"
+            "baseHref": "./",
+            "customWebpackConfig": {
+              "path": "./webpack.config.js"
+            }
           },
...
```

`"builder": "@angular-builders/custom-webpack:browser"` is necessary for the change to work - without it, the updated config will not even pass the syntax validation.

To check if everything is working as expected, let's visit `<your-project-path>/dist/esbuild-loader-ng-cli/`:

![Screenshot 2021-07-18 at 11-34-41 EsbuildLoaderNgCli](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ngasqj2lhhv6unep54es.png)

# The benchmark

So let's compare the default build with the esbuild enhanced one. Unfortunately, the results are inconclusive - you get better speed or better build size (and less configuration headache), so you need to pick what is a priority for you.

## Baseline
As a baseline, let's use an example application as Angular CLI generated it. You can find the code [in the main branch of the repo](https://github.com/marcin-wosinek/esbuild-loader-ng-cli). My only change is that I tweaked the configuration to make the built code work from a subfolder - as describe [here](https://how-to.dev/how-to-make-angular-cli-built-app-work-from-a-subfolder).

The build:
```sh
$ npm run build

> esbuild-loader-ng-cli@0.0.0 build
> ng build

✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.

Initial Chunk Files               | Names         |      Size
main.03b0a30b1c3bb39428d7.js      | main          | 133.79 kB
polyfills.9a3c7adfe54759783845.js | polyfills     |  35.99 kB
runtime.b557d7bc6f5a0a2b7c10.js   | runtime       |   1.02 kB
styles.31d6cfe0d16ae931b73c.css   | styles        |   0 bytes

                                  | Initial Total | 170.81 kB

Build at: 2021-07-18T09:10:42.459Z - Hash: 02058db989498bb0ecd9 - Time: 11882ms
```

The key values:
* build size - **170.81 KB**
* build time - almost 12s

## esbuild-loader build
The building code is available in [the esbuild-loader-minimizer branch](https://github.com/marcin-wosinek/esbuild-loader-ng-cli/tree/esbuild-loader-minimizer). Let's see how it performs:

```sh
$ npm run build        

> esbuild-loader-ng-cli@0.0.0 build
> ng build

✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.

Initial Chunk Files               | Names         |      Size
main.276f4eb0574e0396c077.js      | main          | 437.14 kB
polyfills.4a7829fed9a06cc470d3.js | polyfills     |  39.22 kB
runtime.1d3895c9b4e2bbd6978f.js   | runtime       |   1.08 kB
styles.31d6cfe0d16ae931b73c.css   | styles        |   0 bytes

                                  | Initial Total | 477.43 kB

Build at: 2021-07-18T09:31:40.831Z - Hash: 0a22501608de71af5712 - Time: 7657ms
```

The key values:
* build size - **437.14 KB**, about 2.5 bigger
* build time - almost 8s, about 33% faster

# Summary
The build is faster, but unfortunately, the output file is much bigger &, as such, will be less optimal for our final users & transfer usage of our website. We have the same tread-off as in [building with esbuild](https://how-to.dev/how-to-build-angular-cli-application-with-esbuild), but with less extreme values.