DEV Community

hiepxanh
hiepxanh

Posted on

Angular 14 standalone component help reduce bundle size and the story behind you may not know

Angular 14 standalone component help reduce bundle size and the story behind you may not know
Angular 14 reduce bundle size and the story behind you may not know

Hi, this is my experience on my fulltime project https://awread.vn which is using angular 13. This is my production website with 100 users daily. I'm working to migrate this to angular 14 and have discover this problem.

In this post, I will show you. Why main.js bundle is huge? why lazyload is not working? the Import Barrel index.ts problem ?. Then we will have solution for it.

The import barrel problem? Why your angular main.js is huge? how to reduce bundle size?

g-zip and ng build --prod is two solution that very useful to reduce main.js bundle size. That will help you alot. But what if, you did anything, tried your best. and it still not work? using lazyload module. still not work?

Here is the answer for you. Currently I'm upgrade my Angular 13 to Angular 14 project using nx and I find out the "barrel side effect" cause your main.js bundle increase. The solution is simple.
full explain is here https://webpack.js.org/guides/tree-shaking/#conclusion and here https://github.com/angular/angular-cli/issues/16799 and here https://stackoverflow.com/a/59353152/5748537. But I can make it short for you:
you have module "A is small", and "B is Big". you write them in UI library and export both of them in index.ts file. you import A in your project. final bundle still includes B which you dont want.
Why?
because commonjs is not smart. using es2020 is better, they understand which is use and which is not. But they not sure should it be remove or not. you add sideEffects: false to tell them: "clear dead codeis safe". and they did it. Then your bundle is small. this can use in any modern JS framework include React, VueJS
read the issue in angular project about barrel side effect here: https://github.com/angular/angular-cli/issues/16799

So I have a solution for you:

Image description

change your tsconfig.json build module from "module": "commonjs" to "module": "es2020" or esnext (you should use es2020 explain here https://angular.io/guide/migration-update-module-and-target-compiler-options)
add "sideEffects": false to package.json 

Image description

I can reduce 390kb for main.js bundle size just using sideEffects: false on production project using 613 component with design-system in mindset.

Lazyload is just not lazyload!

I have example in the image below. CoreModule is empty, You have TestModule which import something heavy like mat-datepicker. And you think you can reduce main.js bundle size by lazyload?
The answer is: NO.
Event if I use Angular 14 with routing loadComponent ?: NO it still not.
Why?
Image description

this was discuss in https://github.com/angular/angular-cli/issues/15787
Image description

So the explain is:

That's totally wrong assumption, code splitting is based on module, not value, so that @angular/core can only exist in one bundle, in this case main.js. Any lazy bundle could increase the non-tree-shakable parts of @angular/core, resulting in bigger @angular/core remaining, thus bigger main.js bundle size.
As long as your lazy module make any additional usage of @angular/core, @angular/common or rxjs, size of main.js would certainly increased, and the stats seems expected so far.
What is the solution for you? Since the issue was closed and cannot comment. But it just not help me to reduce bundle size. Let see how we can fix it.

solution 1: only use library one time in one page…

"You cannot import library in two location without increase main.js"
Why? For example, mat-datepicker module import in lazyload route A and lazyload route B. how it should be?
webpack detect that it exist in both component. so it decide to put it in common place in this case is main.js. If you only use it in route B which is lazyload. It know that it should not put in main.js and decide to move datepicker to route B instead of common file. So we got the rule:

webpack rule: "Any file will be move to main.js if it was import in two place"
So, if you only use 3rd party library in one page only. it good, webpack and sideEffects false could move that code into lazyload module (or component in v14). Here is my profile page. it's bundle increase from 151kb to 466kb. Since it using only datepicker.

Image description

But what if I have two lazy page, both of them are using datepicker?. ok, we should go to solution 2.

Solution 2: lazyload the component which is heavy

How it work and why I have to lazyload component?
If you import heavy lib like datepicker in two place, you will go to "webpack rule". So what if we lazyload it? It mean you will not import anything and if nothing being import in the build time. it will not go to main.js bundle.
is that true? is that real? Yes. this image I import date picker in two difference place and lazyload it. Using angular 14 and standalone: true. The result of main.js with angular material reduce from 200kb to 39kb. Yes it true

Image description

So how to do this? First wrap datepicker in a standalone component
Image description

Second, lazyload it:

here is the result. before, it stick with profile page
Image description

after it separate a single file instead mix with other and mix with profile page.
Image description

safe from main.js. safe from page. so we have very fast angular.
Welcome to angular 14. Thank you for reading my first article.
about me:
I have 7 years experience in Angular. And currently working on my fulltime project https://awread.vn
contact me: hiepxanh@gmail.com or skype: live:hiepxanh if you need.

Top comments (0)