In this post I will discuss different ways to generate Angular application bundles. I will show examples using Rollup, Webpack and the Closure compiler (ADVANCED_OPTIMIZATION).
Rollup or Webpack is currently the safest choice when picking an application bundler. Both are great bundlers with lots of benefits.
The Closure compiler is still experimental wrt Angular, but the Angular team has made great progress. At the time of writing the latest release is not compatible with the Closure compiler. However, Alex Eagle has made a custom, Closure compatible, build of Angular and RxJs. I am using a fork of his project for my Closure compiler example.
In the following section I will create builds of my sample application using Rollup, Webpack and the Closure compiler.
I don't want to put any of the frameworks at a disadvantage, so here are the configurations used:
Let me know if there are potential optimizations missing.
Next I will do some basic comparisons of the generated bundles.
For the purposes of this sample I have created an application of medium complexity. The application consists of a dynamic ReactiveForm with validation, and a recursive Treeview.
Below is a screenshot of the application.
I have deployed each build on my server in case you want to compare them:
One of the optimization techniques I've will discuss is called Tree Shaking. Tree Shaking is the process of ensuring that unused code is not included in the bundle. In this case that means omitting any unused code from the Angular framework. For more details on Tree Shaking you may want to read one of my other articles.
I have summarized the bundle sizes in the table below:
When we look at the numbers we see that Webpack produced a slightly bigger bundle than Rollup. This difference can be explained by some extra overhead added by Webpack to each bundled module.
Webpack wraps every module in a new function scope as seen below
Adding this function wrapper around each module will result in a slightly bigger bundle.
By contrast, Rollup puts everything in a single function scope like so:
Another difference is that Rollup supports Tree Shaking.
That said, in my sample the size difference is not noteworthy. In larger apps there could perhaps be an impact on performance though.
Nolan Dawson has done much more thorough analysis of this in his excellent The cost of small modules article.
Still, before we judge Webpack, it's important to point out that Webpack is much more feature rich than Rollup.
Webpack makes up for the slightly bigger bundle size with more flexibility. One example of this is code splitting which can be used for lazy loading.
Rollup does not support code splitting, so you are limited to a single file bundle. This might become an issue as your application grows beyond what is practical as a single file download. Webpack will let you split the bundle into multiple files and lazy load on demand.
At 48.4k, the Closure compiler produced the smallest bundle by far.
This is because the Closure compiler goes far beyond just bundling. Rollup does Tree Shaking of a module at the statement level. Examples of statements are classes and functions. Rollup will be able to Tree shake out any unused statements, but it won't be able to evaluate the internals of the statements.
One example of this is unused methods in classes. Any unused methods in included statements will be included in the final bundle. This can even have a compounding effect if these unused methods refer to other modules. In that case the final bundle will include modules that are only referred to by unused methods.
I have an article here that describes how impactful this can be on the bundle size.
The Closure compiler does not suffer from this limitation and will run a much deeper analysis of the code. Through aggressive rewrites, function flattening and inlining it can potentially remove a lot of code.
One specific example in Angular is decorators.
If we inspect a Rollup or Webpack bundle we still see code like this:
This is the original decorator code that is no longer needed. It is not removed by Rollup or Webpack though since it's embedded in included code.
The Closure compiler will however see this as unnecessary code and correctly omit it from the bundle.
What about code splitting?
It turns out that the Closure compiler also supports code splitting, but I have not seen a working sample of this in Angular yet.
I do however have an article here showing how to do code splitting with the Closure Compiler in general.
The Closure compiler is not a new thing. With these amazing results, why have most people never heard of it?
The reason is that the aggressive optimizations come at the cost of several assumptions about your code. Unfortunately most frameworks are incompatible with the Closure compiler.
Another challenge is frameworks with a separate static framework file download. The framework file typically defines global functions used by application code. This contract breaks when Closure compiler renames the code references back to the framework code.
There are however ways to give the Closure compiler cues in the form of externs to tell it to leave certain parts of the code alone. This complicates things though. I have an article here with some common conventions in case you are interested.
All the challenges I mention above perfectly describe Angular 1.x.
I guess there is still work to be done, but I am hopeful that the Closure compiler will become a real option in Angular going forward.
Another framework with fairly decent Closure compiler compatibility is Svelte. I have an article here in case you are interested.