How Did Angular CLI Budgets Save My Day And How They Can Save Yours

Too little or too much of anything can be bad for you! 😉 (Original 📸 by Patrick Fore)

What Are We Going To Learn?

  1. What are the Angular application budgets
  2. What kind of problems can be discovered by using budgets
  3. How can budgets help with these problems
  4. What are the tradeoffs ( cost of using budgets in your application )
  5. How to prevent problems uncovered by budgets to ever happen again

This article is based on a true story! It happened while working on the Angular NgRx Material Starter which recently started using Angular CLI budgets…

What is Angular CLI Budgets?

Budgets is one of the less known features of the Angular CLI. It’s a rather small but a very neat feature!

As applications grow in functionality, they also grow in size. Budgets is a feature in the Angular CLI which allows you to set budget thresholds in your configuration to ensure parts of your application stay within boundaries which you set — Official Documentation

Or in other words, we can describe our Angular application as a set of compiled JavaScript files called bundles which are produced by the build process.

Angular budgets allows us to configure expected sizes of these bundles. More so, we can configure thresholds for conditions when we want to receive a warning or even fail build with an error if the bundle size gets too out of control!

How To Define A Budget?

Angular budgets are defined in the angular.json file. Budgets are defined per project which makes sense because every app in a workspace has different needs.

Thinking pragmatically, it only makes sense to define budgets for the production builds. Prod build creates bundles with “true size” after applying all optimizations like tree-shaking and code minimization.
Example of a budgets defined for an app in angular.json file.
This example budget comes from the Angular NgRx Material Starter project, feel free to check current version of the angular.json file to see latest budget configuration within the full context.

The budgets property is an array which accepts list of budget configuration objects. While there are many possible ways to configure budgets, we should be most interested in configuration per bundle. That way we get the most targeted feedback in case of crossing of a threshold and we will have much easier time figuring out what is the root cause.

Every budget of a type bundle needs a name so that it can be matched to the corresponding bundle. The baseline property describes the expected size of a bundle.

The warning and error properties specify how much can the bundle size deviate from its baseline. For example bundle with the baseline of 100kb will trigger warning of 50kb only if its size is more than 150kb or less than 50kb. The warning then acts as a both min and max threshold.

It is also possible to configure min and max thresholds individually using minimumWarning and maximumWarning properties (same for error)
Follow me on Twitter to get notified about the newest Angular blog posts and interesting frontend stuff

What Can Go Wrong?

Ok, our budget configuration is in place and we start working on some new feature. After some hours we try to run the prod build and get an output like this…

Example of a Angular CLI budgets build error

Oops, a build error! The maximum bundle size was exceeded. This is a great signal that tells us that something went wrong…

  1. We might have experimented in our feature and didn’t clean up properly
  2. Our tooling can go wrong and perform a bad auto-import, or we pick bad item from the suggested list of imports
  3. We might import stuff from lazy modules in inappropriate locations
  4. Our new feature is just really big and doesn’t fit into existing budgets
  5. Something else? Please, share stuff which has caused Angular budgets errors in your applications using the article responses ✏️😉
In case from the “true story”, the problem was caused by a bad import of an operator from RxJS — a 3rd party library which is inseparable part of every Angular application.

How Will Budgets Help?

Our bundle size has increased significantly and we can be pretty sure that something went wrong. What can we do to debug this problem in greater detail? Enter Webpack bundle analyzer or for the more practical-minded folks, npm i -D webpack-bundle-analyzer.

This module enables us to “Visualize size of webpack output files with an interactive zoomable treemap” — Official Docs

It is extremely useful for exploring code that ends up in particular bundle as a result of a build process. We can run it in our Angular CLI applications by running prod build with additional --stats-json flag which generates stats.json file. The file can be then consumed by the analyzer using the following command webpack-bundle-analyzer ./dist/stats.json.

Check out an example of how to implement the analyze functionality using npm scripts in Angular NgRx Material Starter (focus on build:prod and analyze scripts).

The output of the analyzer contains all modules but we’re only interested in things which don't look quite right. Stuff like major duplications or modules in a bundle where they don’t belong.

Example of comparing outputs of the webpack-bundle-analyzer for discovering of the bad imports that cause significant bundle size increase
In case from the “true story”, the analysis of the bundles content helped to localize problematic import by providing hints about the library (rxjs) and package name (internal)
Example of a bad RxJS import that increased bundle size by more than 150kb

Budgets Sound Great, What Is The Catch?

As the popular saying goes, nothing is for free…

Especially in the field of software engineering, every decision we make results in some kind of a trade-off. So what is the cost of maintaining budgets? To keep it short, it’s pretty low…

First, we have to establish initial budgets configuration based on the current size of all relevant application bundles and their error and warning thresholds.

I would suggest focusing on simplicity instead of being 100% precise. It’s faster to look over budgets with round numbers like 700kb instead of hurting your eyes by scanning large amount of random numbers…
Budgets with reasonably set warning and error thresholds shouldn’t get triggered during the course of a normal development

Adding a new minor feature or fixing a small bug will probably NOT result in tens of kilobytes of extra bundle payload.

On the other hand, adding of a new fancy 3rd party library just might set the budgets alarm off and that’s a good thing…

  1. It can makes us aware of the real cost of using that tiny utility which pulls in the rest of the library
  2. It can lead into a team discussion about how to address the need for this specific functionality (eg do we really need to add a dependency or would it be easier to implement it ourselves?)

Once we are aware of the reason for a bundle size increase and we’re happy with it all, we have to do is to simply adjust budget with the new values …

Permanent Solution To Problems Found By Budgets

In our previous example, the budgets were catching bad RxJS import which caused huge increase in the bundle payload size. Removing the bad import fixes current problem but doesn’t really help with any future occurrences of similar problems.

Luckily, it is possible to update related tslint rule called import-blacklist with the offending import rxjs/internal/operators. This is a permanent solution to the problem uncovered by the budgets. Great!

Tslint rule import-blacklist is great for preventing undesired imports in the whole workspace

We established that the budget errors can arise as a result of bad imports of 3rd party libraries. Adding to that, bad imports can happen also in the application code itself. Especially in very large enterprise applications.

Imagine a situation where we have couple of large lazy-loaded modules. What if we tried to import and use service from a lazy module in the eagerly loaded parts of application? I mean, of course just by a mistake. 😉

Angular compiler would have no other choice than to pull that service and all related dependencies into the main bundle!

Depending on a particular situation this could lead into significant increase of the main bundle size. On the other hand, such a problem is much harder to detect and debug compared to weird looking imports of 3rd party library.

I am aware only about one open source solution that can help us to prevent this kind of problems. The solution is using nrwl/nx workspaces. It provides us with the ability to assign tags to our own apps and project libraries. This tags enable us to define rules about what can be imported from where with a provided nx-enforce-module-boundaries Tslint rule. It might be an overkill for a small apps but can definitely pay off in an enterprise environment!

Angular budgets are great! Use them and keep your apps lean and fast!

That’s It For Today!

I hope you enjoyed this article! Please support this guide with your 👏👏👏 to help it spread to a wider audience 🙏. Please, don’t hesitate to ping me if you have any questions in the article responses or on Twitter @tomastrajan

And never forget, future is bright
Obviously the bright future (📷 by Chris Barbalis)