CONTACT
arrow_left Back to blog

Build time benchmarking using Angular CLI

Build time benchmark of monolithic application build using Angular CLI

In this article, we’ll take a look at the impact of different build options of the Angular CLI on the build time of a monolithic application. Building is usually on a critical path of the continuous integration (CI) pipeline—that is, any changes to build duration impact the total CI time. The Angular team makes a few build optimizations available for developers, and in this post, we’ll take a look at their impact.

Measurements

To make the data reproducible and relevant, I’ll run the test builds with CircleCI. Using external servers for the build isolates the measurement from my local environment. At the same time, the results come from machines that others are using for building as well.

The key measurement I’ll pay attention to is the time needed to run the npm run build command:

Screen shot of build summary at CircleCI

At this level, I expect to see the most direct results of the optimization we use. If we manage to cut the build time in half, then we should see this impact here.

Another value to consider is the time of a whole build job. This will be especially important for optimizations that come with positives and negatives: for example, caching can speed up some operations, but part of the gain will be eaten by the downloading and uploading cache.

Screen shot of job result at CircleCI

The last measurement for us to consider is the overhead: the difference between build script and build job. It will contain everything that is done before or after the build: restoring the cache, installing dependencies, saving the cache. With this number, we should see clearly the downsides of some optimization.

I’ll repeat each test build 5 times under the same circumstances and use the average of the measurements as the measurement value. In this way, I’ll be able to reduce a bit of random noise in the data without turning it into a full scientific experiment.

Application structure

As a benchmark, I’m using the build time of a simple application generated and built with the Angular CLI. The application models the real world complexity by including 1000 components. This approach is inspired by Nrwl’s Angular benchmark. Each component is very simple:

src/app/component1/component1.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-component1',
  templateUrl: './component1.component.html',
  styleUrls: ['./component1.component.css']
})
export class Component1Component {

}

src/app/component1/component1.component.html:

<p>component1 works!</p>

And the whole application just displays each component one after another:

Application screenshot

The biggest blind spot is the lack of third-party libraries. This is something that almost all real-world applications do, but including it in the benchmark would tie the results to the set of libraries used in the example application.

Build options

In my test, I’m measuring the build time in the following setups.

Baseline

This is the standard build as it’s generated by Angular CLI. Important features are as follows:

  • it uses webpack under the hood
  • it lacks ng cache for CI—so each build during CI starts from scratch
  • the build job provides cache for npm install

esbuild

esbuild is a promising bundler with much better performance than Webpack: according to its own website, it’s up to 100 times faster. Because of its speed, since version 14, the Angular team has provided an experimental builder based on esbuild. It was recently improved in version 15. It can be turned on easily, with just 1 line change in angular.json:

       "architect": {
         "build": {
-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular-devkit/build-angular:browser-esbuild",
           "options": {

Note! So far, this change affects only the build—the development server is still using Webpack. This is likely to change in version 16.

ng caching

For local builds, Angular CLI caches build results to save time on reruns. By default, this feature is turned off for CI. To use caching during CI, we need two changes:

  • changing the cache configuration from local-only, and
  • reading and saving the cache during CI.

You can turn on caching with two commands:

ng config cli.cache.environment all
ng cache enable

Managing cache files for CI will require changes in configuration. You can find my change for CircleCI here.

TypeScript 5

TypeScript 5 promises up to 10% improvement for the build time. It’s not yet available for Angular applications, but it’s on track to come in version 16. For my tests, I’m updating Angular to pre-release version 16.0.0-next.4.

Data

The builds I’ve run measured as follows:

npm run build (s) Build job (s) Overhead (s)
baseline 57.8 92 34.2
esbuild 14.4 50.2 35.8
hot build cache 31.4 67 35.6
cold build cache 66.8 104.2 35.6
TypeScript 5 55 84.8 29.8

The baseline time needed to complete npm run build was on average 57.8s. The biggest improvement we can see is with switching to esbuild: it becomes 4 times faster and takes only 14.4s to build. Both builds have very similar overhead. With esbuild, the build job takes 56% of time that was needed in the baseline case—an excellent improvement.

Another optimization—caching of the Webpack build—looks very promising as well. Because there are more files cached, I expected a clear increase in the overhead. It went from 34.2s to 35.6s, but this difference could be statistically insignificant. Caching of the build has two effects:

  • speed increases when cache is “hot”—with files already in place, the build job finishes in 31.4s instead of 57.8s.
  • speed decreases when the cache is “cold”—most likely, the build takes more time because there are way more files to be written on the disk. Build went up from 57.8s to 66.8s.

Lastly, an interesting case is updating to TypeScript 5. The time needed to complete the npm run build lowered slightly from 57.8s to 55.5s. At the same time, the overall build job took 84.5s instead of 92s, which is a better improvement than the build script alone. This could be explained by the more efficient installation—maybe TypeScript 5 or other v16 dependencies are faster to install.

Conclusions

  1. Check out esbuild—if it won’t introduce regressions to your code, you can immediately enjoy faster builds!
  2. If esbuild doesn't work in your case, try enabling caching for Webpack on CI.
  3. Don’t hold your breath for the speed increase that comes with switching to TypeScript 5.

You can find our test application here.

Looking for more support for your Angular projects?

Our team at DevIntent is highly experienced in web development, including framework migration and updates. Check out our offerings or reach out.