-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Increased initial main.js bundle size in v9 - mainly due to @angular/material packages #19610
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Heya, thanks for the report! The current situations we know this might happen in are documented in https://angular.io/guide/ivy-compatibility#payload-size-debugging. Can you take a look at case 1. and 2. just in case they apply in your case? I'd like to take a look at your project if possible. I think the most straightforward way to share it would be a private github repo (these are now free) that you invite me to. Would that work? |
@filipesilva |
@bri1990 looking at the project at the moment. Can you tell me in which of the apps you saw the increased bundle size? Since you have two apps and many shared libraries, I think you are suffering from case 2 in https://angular.io/guide/ivy-compatibility#payload-size-debugging. Can you try the approach described in angular/angular-cli#16799 (comment) and see if it works please? |
@filipesilva in the edu-app, it uses both the @education as the @banax folder (ng build --prod --project edu-app, we currently only use that one in that repo),
Regarding case 2: this gets us very close to the v8 build size |
It's not a great idea to add it in the main toplevel one, because your apps do have at least side effects in |
I tried putting these various package.json and saw a main bundle of 1.09 MB, which is still larger than the 1.02 MB with View Engine. I've got to dig a bit deeper. |
Hi @filipesilva, thanks for the hint! will implement it. I hope you are able to spot the problem:) I must admit, I expected angular v9 to reduce the initial load by quite a bit. Are we doing something wrong here? I've seen some examples elsewhere but the initial load remained the same in most cases (examples were done in 9.R.C) In our case we have quite a large css bundle, I believe 160 kb is for theming alone (dark + normal modus), so some room to improve here. gr |
@filipesilva using perhaps it is use full information |
another question is, do you still need access to the repo? |
@bri1990 are you only seeing the problems when serving on nginx and not on I still need access to the repo because I wanted to find the reason for the size difference. But I also have it on disk from the initial investigation so you can remove my access and I will ask it again if relevant. |
Hi again @filipesilva, no it only happends on nginx, ng serve runs fine. We find it quite hard to debug, so we removed the
Will post our findings regarding the nginx problem here |
Earlier it occurred to me, and meanwhile I forgot, that that might be unused code in the app folder itself, so I also added |
@filipesilva are there any reports regarding the addition of We only see some js files (main, polyfills, but not the complete waterfall) served after we build the project and try to serve the compiled bundle (dist) either with nginx or http-server (the node package). this means that our app gets stuck, we only see the splash screen in our case so we are forced to remove the |
@bri1990 can you tell me the paths you added those |
we tried multiple ways but they all seem to have the same effect: With this code (diff names) than we tried to add it only in the main package.json |
Ok, some of those are the "wrong" places. So for instance Essentially you should look at toplevel code and ask "if this code didn't run because I didn't import the file anymore, would things behave differently or break?". If the answer is yes, it's has side effects. In Angular CLI projects,
Both In your particular project, these are the places where you should put a
But you can't do |
Looked a bit more into Materials List and why it showed such a different size between Ivy and VE. To take a good look at a good I made a debug build using I found From this I was able to make a repro:
The Ivy build has a main bundle of 232 kB, while the VE build has a main bundle of 221 kb. Still tracking down the reason for this difference in since, but having a minimal example helps a lot. There might be other things going on besides this though. |
Ok, I dissected the https://github.com/filipesilva/ivy-material-list-size example and added an explanation. Now that we know what's happening with a bit more detail we'll try to address it. ivy-material-list-sizeThis repo shows a size regression with Ivy found in https://github.com/angular/angular-cli/issues/16866. git clone https://github.com/filipesilva/ivy-material-list-size
cd ivy-material-list-size
yarn
yarn repro The first build will use Ivy and output to What's happening?Given the following template: <mat-nav-list>
<mat-list-item>
hello
</mat-list-item>
</mat-nav-list> Ivy produces a 232 kB output while VE produces a 221 kB output, resulting in a 11 kB increase. But if you change the template to: <mat-nav-list>
hello
</mat-nav-list> Ivy produces a 194 kB output while VE produces a 212 kB output, resulting in a 18 kB decrease. You can use the command below to produce debug versions of the production bundles.
The The Ivy file retained the following classes:
The VE file retained the following classes:
Notably, the VE file does not reference either return MatListItem.\u0275fac = function(t) {
return new (t || MatListItem)(\u0275\u0275directiveInject(ElementRef), \u0275\u0275directiveInject(ChangeDetectorRef), \u0275\u0275directiveInject(list_MatNavList, 8), \u0275\u0275directiveInject(list_MatList, 8));
}, MatListItem.\u0275cmp = \u0275\u0275defineComponent({
type: MatListItem,
selectors: [ [ "mat-list-item" ], [ "a", "mat-list-item", "" ], [ "button", "mat-list-item", "" ] ],
contentQueries: function(rf, ctx, dirIndex) {
var _t;
1 & rf && (\u0275\u0275contentQuery(dirIndex, list_MatListAvatarCssMatStyler, !0),
\u0275\u0275contentQuery(dirIndex, list_MatListIconCssMatStyler, !0), \u0275\u0275contentQuery(dirIndex, core_MatLine, !0)),
2 & rf && (\u0275\u0275queryRefresh(_t = \u0275\u0275loadQuery()) && (ctx._avatar = _t.first),
\u0275\u0275queryRefresh(_t = \u0275\u0275loadQuery()) && (ctx._icon = _t.first),
\u0275\u0275queryRefresh(_t = \u0275\u0275loadQuery()) && (ctx._lines = _t));
},
hostAttrs: [ 1, "mat-list-item" ],
hostVars: 4,
hostBindings: function(rf, ctx) {
2 & rf && \u0275\u0275classProp("mat-list-item-avatar", ctx._avatar || ctx._icon)("mat-list-item-with-avatar", ctx._avatar || ctx._icon);
},
inputs: {
disableRipple: "disableRipple"
},
exportAs: [ "matListItem" ],
features: [ \u0275\u0275InheritDefinitionFeature ],
ngContentSelectors: list_c2,
decls: 6,
vars: 2,
consts: [ [ 1, "mat-list-item-content" ], [ "mat-ripple", "", 1, "mat-list-item-ripple", 3, "matRippleTrigger", "matRippleDisabled" ], [ 1, "mat-list-text" ] ],
template: function(rf, ctx) {
1 & rf && (\u0275\u0275projectionDef(list_c1), \u0275\u0275elementStart(0, "div", 0),
\u0275\u0275elementStart(1, "div", 1, void 0), \u0275\u0275elementEnd(), \u0275\u0275projection(2),
\u0275\u0275elementStart(3, "div", 2), \u0275\u0275projection(4, 1), \u0275\u0275elementEnd(),
\u0275\u0275projection(5, 2), \u0275\u0275elementEnd()), 2 & rf && (1, selectIndexInternal(getTView(), getLView(), getSelectedIndex() + 1, getCheckNoChangesMode()),
\u0275\u0275property("matRippleTrigger", ctx._getHostElement())("matRippleDisabled", ctx._isRippleDisabled()));
},
directives: [ core_MatRipple ],
encapsulation: 2,
changeDetection: 0
}), MatListItem; Additionally, Ivy contains factory code (the |
@filipesilva. Any updates on this? I have same issues with them above. My main.js is bigger but the bundle size is smaller. I have implemented the steps in https://angular.io/guide/ivy-compatibility#payload-size-debugging. I have put Is there a problem on Angular 9 Ivy? Thanks a lot |
@jtan80813 we're still looking into how we can address the particular problem in this issue, but things are moving along. |
@filipesilva. Thats great! Pls update us here if its fixed. Thanks! |
Same issue here:
... which is due to Ivy putting more features into synchronous chunk than ViewEngine.
|
Any update on this, I think Material is the most affected:
|
Anyone got a chance to run Coverage Tool (https://developers.google.com/web/tools/chrome-devtools/coverage) in Chrome Dev Tools for Angular app? After running this against my website and navigating to all sections of the website it says "32% belongs to functions that have not yet been executed" which means that of 1.10Mb main bundle, ~360kb should have been tree-shaken. Right? or am I misinterpreting it? |
It's not clear how foolproof that coverage tool is. Wouldn't you need to have it navigate through and use every function in your app to be able to determine the true coverage? Does it allow you to dig deeper to see which modules/classes/functions/etc aren't used? |
Just to clarify my last comment - just because a coverage based tool identifies code as dead, it doesn't mean that the code is eligible for tree-shaking and dead code elimination. The current state of tooling in the JavaScript ecosystem is still very primitive, and in order to remove unused code, we have to refactor the code in complex ways to enable the tooling to process is correctly. Example: function alwaysUsed() {
if (isFancyApp()) {
doSomethingRare()
}
}
function doSomethingRare() {
// ... lots of code here...
}
function isFancyApp() {
// ... some custom condition ...
} In the code above there is no easy way for the build tooling to understand how the |
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Angular Material list items currently optionally inject a parent `MatNavList` or `MatList`. This has the downside of retaining these tokens at runtime because they are used for dependency injection as values. We can improve this by using so-called light-weight tokens. These allow us to continue injecting parent list or nav-lists, but without the risk of retaining the `MatList` and `MatNavList` classes with their factories. This was already the case before Angular v9 with View Engine, but the issue significance increases with Ivy where factories are now directly attached to the classes (such as `MatList` or `MatNavList`). Using light-weight tokens avoids this issue and gives us an additional size improvement. Notably this won't be an improvement when an application uses both the nav-list and standard `MatList`. Related to https://github.com/angular/angular-cli/issues/16866. More context on light-weight tokens in: https://hackmd.io/@mhevery/SyqDjUlrU#.
Currently Angular internally already handles `InjectionToken` as predicates for queries. This commit exposes this as public API as people already relied on this functionality but currently used workarounds to satisfy the type constraints (e.g. `as any`). We intend to make this public as it low-effort, and it's a signficant key part for the use of light-weight tokens as described in the upcoming guide: angular#36144. In concrete, applications might use injection tokens over class for both DI and queries, because otherwise such class references for optional queries or DI always cause these classes to be retained. This was also an issue in View Engine, but now with Ivy, this pattern became worse as factories could be directly attached to retained classes (ultimately ending up in the production bundle, while unused). More details in the light-weight token guide and in: https://github.com/angular/angular-cli/issues/16866. Closes angular#21152.
Currently Angular internally already handles `InjectionToken` as predicates for queries. This commit exposes this as public API as people already relied on this functionality but currently used workarounds to satisfy the type constraints (e.g. `as any`). We intend to make this public as it low-effort, and it's a signficant key part for the use of light-weight tokens as described in the upcoming guide: angular#36144. In concrete, applications might use injection tokens over class for both DI and queries, because otherwise such class references for optional queries or DI always cause these classes to be retained. This was also an issue in View Engine, but now with Ivy, this pattern became worse as factories could be directly attached to retained classes (ultimately ending up in the production bundle, while unused). More details in the light-weight token guide and in: https://github.com/angular/angular-cli/issues/16866. Closes angular#21152.
Currently Angular internally already handles `InjectionToken` as predicates for queries. This commit exposes this as public API as developers already relied on this functionality but currently use workarounds to satisfy the type constraints (e.g. `as any`). We intend to make this public as it's low-effort to support, and it's a significant key part for the use of light-weight tokens as described in the upcoming guide: angular#36144. In concrete, applications might use injection tokens over classes for both optional DI and queries, because otherwise such references cause classes to be always retained. This was also an issue in View Engine, but now with Ivy, this pattern became worse, as factories are directly attached to retained classes (ultimately ending up in the production bundle, while being unused). More details in the light-weight token guide and in: https://github.com/angular/angular-cli/issues/16866. Closes angular#21152. Related to angular#36144.
Currently Angular internally already handles `InjectionToken` as predicates for queries. This commit exposes this as public API as developers already relied on this functionality but currently use workarounds to satisfy the type constraints (e.g. `as any`). We intend to make this public as it's low-effort to support, and it's a significant key part for the use of light-weight tokens as described in the upcoming guide: angular#36144. In concrete, applications might use injection tokens over classes for both optional DI and queries, because otherwise such references cause classes to be always retained. This was also an issue in View Engine, but now with Ivy, this pattern became worse, as factories are directly attached to retained classes (ultimately ending up in the production bundle, while being unused). More details in the light-weight token guide and in: https://github.com/angular/angular-cli/issues/16866. Closes angular#21152. Related to angular#36144.
Currently Angular internally already handles `InjectionToken` as predicates for queries. This commit exposes this as public API as developers already relied on this functionality but currently use workarounds to satisfy the type constraints (e.g. `as any`). We intend to make this public as it's low-effort to support, and it's a significant key part for the use of light-weight tokens as described in the upcoming guide: angular#36144. In concrete, applications might use injection tokens over classes for both optional DI and queries, because otherwise such references cause classes to be always retained. This was also an issue in View Engine, but now with Ivy, this pattern became worse, as factories are directly attached to retained classes (ultimately ending up in the production bundle, while being unused). More details in the light-weight token guide and in: https://github.com/angular/angular-cli/issues/16866. Closes angular#21152. Related to angular#36144.
I'm transferring this to the components repo because most of the actions need to be taken in the @angular/material and @angular/cdk codebases (in addition to landing angular/angular#37506 in the core) |
@naveedahmed1 Do you have any application we could look into? @filipesilva's investigation on list seems correct, but I'd like to double-check on other components you mentioned. e.g. tabs |
Thanks @devversion I have added you to a private repo, will it work? |
@naveedahmed1 yeah, that will work, thanks! your help on this is much appreciated! |
@devversion most welcome. If you receive error of No Providers for MatDialog, it would be due to this issue #19335, just add MatDialogModule to the providers and it should work. |
@naveedahmed1 I looked into it. Thanks for sharing your numbers and app. It looks like View Engine and Ivy deviate that much in your comparison because factories are directly attached to the classes, while View Engine had a separate chunk for factories (e.g. Percentage-wise, these 4kb are still noticeable as that signifies a ~12% increase. The actual size differences come from factories being generated for directives. This wasn't the case in View Engine as there weren't any factories for directives. Instead, host bindings, DI etc. were directly inlined to the host factory if a directive is used. A directive factory in Ivy by average seems to be larger by around ~450b (compared to the VE inlined code). There seem to be 9 directive (base) classes in tabs, resulting in the ~4kb increase. The actual size benefit with Ivy surfaces here as multiple use of, for example |
@devversion thank you so much for your time and looking in to this, really appreciate. |
@naveedahmed1 Anytime! Thanks again for the great information/reproduction you provided! 🎉 I'm going to close this issue as we landed our refactor changes in Angular Components. See #19576. Other than that, a slight increase in Angular Components chunks might be noticeable "at first glance", but in reality this is due to Ivy's architecture which later on results in overall size benefits (due to reduced factory code duplication etc.). Please see my comment above for a concrete example. Also if you continue to discover any significant size impact, please create a new issue! We'd love to look into it! |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
🐞 Bug report
Command (mark with an
x
)Is this a regression?
yes,
Description
Our initial bundle increased after the upgrade to angular v9. Below 2 screenshots made with webpack-bundle-analyzer, both are build with the --prod tag on. The complete size of our app decreased in size, but the main bundle increased slightly.
reporting this
@filipesilva who mentioned the following
In our case the initial bundle from the @angular module in angular 9.0.0 is increased by 170 kb (parsed) compared to the build size in angular 8.3.20
ANGULAR 9.0.0 bundle overview

ANGULAR 8.3.20 bundle overview

🔬 Minimal Reproduction
its a private repo, but can share an bitbucket account via a private message if needed
🔥 Exception or Error
budle size difference
Angular 9:
Angular 8:
🌍 Your Environment
Anything else relevant?
Similar issues: angular/angular-cli#16989
The text was updated successfully, but these errors were encountered: