Angular-cli: Increased main.js bundle size but less lazy chunks after updating to angular 9.0.0-rc.1

Created on 11 Nov 2019  路  58Comments  路  Source: angular/angular-cli

馃悶 Bug report

Command (mark with an x)


- [ ] new
- [x] build
- [ ] serve
- [ ] test
- [ ] e2e
- [ ] generate
- [ ] add
- [ ] update
- [ ] lint
- [ ] xi18n
- [ ] run
- [ ] config
- [ ] help
- [ ] version
- [ ] doc

Description

After updating from Angular 8.2.3 to 9.0.0-rc.1 the bundle size was increased for main module and some of the feature modules:
Before it was:
bundle_before9

After updating to Angular 9.0.0-rc.1 it is
image

馃敩 Minimal Reproduction

running ng build --prod --namedChunks with following configuration:

            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "commonChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "1mb",
                  "maximumError": "2mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ],
              "serviceWorker": true,
              "ngswConfigPath": "ngsw-config.json"
            }
          }

馃實 Your Environment


Angular CLI: 9.0.0-rc.1
Node: 10.15.3
OS: win32 x64
Angular: 9.0.0-rc.1
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router, service-worker

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.0-rc.1
@angular-devkit/build-angular     0.900.0-rc.1
@angular-devkit/build-optimizer   0.900.0-rc.1
@angular-devkit/build-webpack     0.900.0-rc.1
@angular-devkit/core              9.0.0-rc.1
@angular-devkit/schematics        9.0.0-rc.1
@angular/cdk                      8.2.3
@angular/material                 8.2.3
@ngtools/webpack                  9.0.0-rc.1
@schematics/angular               9.0.0-rc.1
@schematics/update                0.900.0-rc.1
rxjs                              6.5.3
typescript                        3.6.4
webpack                           4.41.2

devkibuild-angular medium repro steps regression bufix

Most helpful comment

I've looked into this and have arrived at a minimal reproduction and explanation in https://github.com/filipesilva/factory-code-location


Factory code location differences between Ivy and View Engine

This application contains the following structure:

  • a shared ngmodule called SharedModule containing a component called SharedComponent
  • two lazy routes (lazy-one and lazy-two) that import SharedModule and use SharedComponent
  • a root AppModule that also imports SharedModule
  • SharedComponent is not used by any template outside the lazy routes

You can change between Ivy and VE by flipping the "enableIvy": true, switch in tsconfig.json.

Running ng build --prod with Ivy shows the following bundles:

chunk {0} runtime.js, runtime.js.map (runtime) 2.23 kB [entry] [rendered]
chunk {1} main.js, main.js.map (main) 262 kB [initial] [rendered]
chunk {2} polyfills.js, polyfills.js.map (polyfills) 36 kB [initial] [rendered]
chunk {3} styles.css, styles.css.map (styles) 38 bytes [initial] [rendered]
chunk {4} 4.js, 4.js.map () 852 bytes  [rendered]
chunk {5} 5.js, 5.js.map () 852 bytes  [rendered]
Date: 2019-11-22T14:22:51.177Z - Hash: f09b623e86f3d80f4000 - Time: 33745ms

Running ng build --prod with VE shows the following bundles:

chunk {0} runtime.js, runtime.js.map (runtime) 2.24 kB [entry] [rendered]
chunk {1} common.js, common.js.map (common) 530 bytes  [rendered]
chunk {2} main.js, main.js.map (main) 263 kB [initial] [rendered]
chunk {3} polyfills.js, polyfills.js.map (polyfills) 36 kB [initial] [rendered]
chunk {4} styles.css, styles.css.map (styles) 38 bytes [initial] [rendered]
chunk {5} 5.js, 5.js.map () 1.17 kB  [rendered]
chunk {6} 6.js, 6.js.map () 1.17 kB  [rendered]
Date: 2019-11-22T14:23:43.985Z - Hash: 90d6a92b420719382f49 - Time: 30408ms

The main difference is the common.js bundle.
In VE this bundle contains the template factory for SharedComponent ("shared-component works!").
In Ivy this template factory is in main.js instead.

However, Ivy will also have the common.js If you remove the SharedModule import from AppModule.

What's happening

To understand what's happening it is important to understand one important difference between Ivy and VE:

  • in VE, compiling a component would generate two files: the original transpiled file and a .ngfactory.js file containing the template factory
  • in Ivy, compiling a component generates only the original transpiled file

In this example SharedComponent is imported via SharedModule both through static imports in AppModule and in the lazy loaded modules.
However, it is not used in any templates outside of lazy routes.

This leads to the following difference between Ivy and VE:

  • in VE, the original SharedComponent class is in main.js but the .ngfactory.js file is in common.js
  • in Ivy, there is only the original SharedComponent class in main.js

If you remove the static import for SharedModule in AppModule there is no longer a chain of static imports in the main module for SharedComponent so it will always be in common.js, even in Ivy.

What you can do right now

If you find that with Ivy your main bundle gets larger while the other bundles get smaller, verify that the components and ngmodules that you want to be lazy loaded are only imported in lazy modules.
Anything that you import outside lazy modules can end up in the main bundle.

Meanwhile we'll try to figure out if there's something we can do in Angular itself to help.

All 58 comments

Any chance of reproduction please?

I will try this afternoon, but its little bit hard as its a customer's project

@wambowams just to be really sure, can you show the sizes on disk for the dist folder? Sometime in 8 we had a bug where the ng build reported sizes weren't correct.

I hope this comment can help.
Faced with increasing main module size(in es2015) from 1.06 to 1.15 MB (and es5 size increased too), but all other modules decreased. Full application size decreased from 3.44 to 2.67 MB.
webpack-bundle-analyzer shows that it caused by angular/material. it increased from 127 to 200 kB and increase main module.
core and cli is 9 RC1,
material and cdk is 9 RC0

p.s. File sizes at disk are increased, checked this.

@parys can you check if you have the same number of files? I wouldn't be surprised if only of the lazy modules had been merged with the main module.

@filipesilva you are right.
86 files (es5 + es2015) in angular 9 rc1
90 files (es5 + es2015) angular 8.2.13
just angular files, skipped css and service-worker files (same count).
checked again, my files (src) decreased ~ 40kB
animations + platform-browser decreased ~10kB
common increased ~24kB.
forms increased ~15kB
router increased ~5kB
core increased ~7kB
-50(-40-10) and +52(24+15+5+7)
cdk increased ~13kB
material increased ~82kb
zone.js, hummerjs, rxjs same size
so as I see angular/material is guilty of increasing size by 90kB.
all angular packages + my main source is around same size.
as I see no one module merged to main. It combined between them at lazy load module level.

@parys Thank u for the tip, I have multiple material imports in different lazy loaded modules.
@filipesilva I will check the material problem and the disk filesize tomorrow at work

I have a simmiliar experience

9.0.0-rc.1

```chunk {3} polyfills-es5.54602c8785569e67e22e.js (polyfills-es5) 125 kB [initial] [rendered]
chunk {2} polyfills-es2015.c7a30df4bc9d877b298c.js (polyfills) 35.9 kB [initial] [rendered]
chunk {8} 8-es2015.ada4e33bfaa3f31731ae.js () 10.1 kB [rendered]
chunk {8} 8-es5.ada4e33bfaa3f31731ae.js () 10.7 kB [rendered]
chunk {9} 9-es2015.709f406ec03c989f0f1f.js () 4.28 kB [rendered]
chunk {9} 9-es5.709f406ec03c989f0f1f.js () 4.43 kB [rendered]
chunk {11} 11-es2015.226745742bd2ed46cdb3.js () 1.08 kB [rendered]
chunk {11} 11-es5.226745742bd2ed46cdb3.js () 1.21 kB [rendered]
chunk {0} runtime-es2015.b97001bcf5aa06731258.js (runtime) 2.43 kB [entry] [rendered]
chunk {0} runtime-es5.b97001bcf5aa06731258.js (runtime) 2.43 kB [entry] [rendered]
chunk {7} 7-es2015.6bd84e6ce375884a9572.js () 19.6 kB [rendered]
chunk {7} 7-es5.6bd84e6ce375884a9572.js () 20.3 kB [rendered]
chunk {13} 13-es2015.a42b4b1c7354dfe6f2ac.js () 2.41 kB [rendered]
chunk {13} 13-es5.a42b4b1c7354dfe6f2ac.js () 2.91 kB [rendered]
chunk {12} 12-es2015.d5800d5600a902e895f5.js () 5.34 kB [rendered]
chunk {12} 12-es5.d5800d5600a902e895f5.js () 5.85 kB [rendered]
chunk {6} 6-es2015.59e69d5e0f464084389e.js () 5.73 kB [rendered]
chunk {6} 6-es5.59e69d5e0f464084389e.js () 6.35 kB [rendered]
chunk {10} 10-es2015.e06cb514cf8a21185f7d.js () 55.7 kB [rendered]
chunk {10} 10-es5.e06cb514cf8a21185f7d.js () 59.1 kB [rendered]
chunk {5} 5-es2015.a3e74ebf9abaa7a35ba5.js () 109 kB [rendered]
chunk {5} 5-es5.a3e74ebf9abaa7a35ba5.js () 111 kB [rendered]
chunk {1} main-es2015.afec893c92139b333131.js (main) 1.83 MB [initial] [rendered]
chunk {1} main-es5.afec893c92139b333131.js (main) 2.03 MB [initial] [rendered]
chunk {4} styles.f7c48dea9a89feb2b99e.css (styles) 74.4 kB [initial] [rendered]
chunk {scripts} scripts.03e042f1f102bf0e2ed8.js (scripts) 19.9 kB [entry] [rendered]
Date: 2019-11-14T07:40:19.035Z - Hash: 4d5c0fbe7ba52a60a6cb - Time: 154070ms


exact numbers on disk

![image](https://user-images.githubusercontent.com/17769458/68836899-ada2cc80-06bb-11ea-9fd8-c825ac8585e5.png)


vs 8

chunk {0} common-es2015.f65f5aa566db4e06d105.js (common) 9.84 kB [rendered]
chunk {1} runtime-es2015.dc5fd48940e2007b44fd.js (runtime) 4.49 kB [entry] [rendered]
chunk {2} 2-es2015.b66890fe6cb0d0ba93d2.js () 57.8 kB [rendered]
chunk {3} 3-es2015.623227132f5ef6839c15.js () 20 kB [rendered]
chunk {4} main-es2015.43343003de40fbc4ec5e.js (main) 2.21 MB [initial] [rendered]
chunk {5} polyfills-es2015.add85f0e35cf86eeff4a.js (polyfills) 63.4 kB [initial] [rendered]
chunk {6} polyfills-es5-es2015.5d4ed357ffea17c6a0dd.js (polyfills-es5) 225 kB [initial] [rendered]
chunk {7} styles.e5e1b71b2548c50cb6c5.css (styles) 72.3 kB [initial] [rendered]
chunk {8} 8-es2015.6bd7263fab8d903b0fb9.js () 254 kB [rendered]
chunk {9} 9-es2015.478af0fa902e9a4f6539.js () 194 kB [rendered]
chunk {10} 10-es2015.1be5d5f2474cd46c8288.js () 59.8 kB [rendered]
chunk {11} 11-es2015.dffb2a20c94c68a0bf1c.js () 28 kB [rendered]
chunk {12} 12-es2015.321a3b949196c255ca8e.js () 19.4 kB [rendered]
chunk {13} 13-es2015.0a30a70a9c35bf08dddb.js () 156 kB [rendered]
chunk {14} 14-es2015.9cf10db117bbba60ae53.js () 9.26 kB [rendered]
chunk {15} 15-es2015.9482769a0e8d8a81eb5b.js () 27.7 kB [rendered]
chunk {16} 16-es2015.4dcc1c7a5105ce379783.js () 12.8 kB [rendered]
chunk {scripts} scripts.03e042f1f102bf0e2ed8.js (scripts) 19.8 kB [entry] [rendered]
Date: 2019-11-12T10:53:35.219Z - Hash: ede763d16e9e87e7cc2b - Time: 200420ms
```

in 8 the numbers reported are wrong ...

exact numbers disk v8

image

@elvirdolic I think something similar happened in your case: version 8 has 13 application js bundles, while version 9 has only 10 application js bundles. The extra size seems to have come from code moving from some of the lazy bundles back into the main chunk.

@filipesilva I checked the file sizes. These are my results:
For Version 8.2.13:
bundle_before9
With size on the disk:

14.11.2019  14:33            60.658 0-es5.566bec7d8e9f9cda780a.js
14.11.2019  14:34            52.826 1-es2015.e9fd6dadef4caff07749.js
14.11.2019  14:33            19.960 5-es5.36e53960b8db97ac7f91.js
14.11.2019  14:34            19.751 6-es2015.4e348f6d41fafc26f200.js
14.11.2019  14:33             3.006 6-es5.c07b99f036dd7c321da9.js
14.11.2019  14:34             2.865 7-es2015.2bf07606f9c036fbb354.js
14.11.2019  14:33           120.951 7-es5.68d52a09dc28bd20e670.js
14.11.2019  14:34           112.920 8-es2015.3f0e8b4086b9f6ef70cc.js
14.11.2019  14:33            61.407 8-es5.82c8181b7341226961bb.js
14.11.2019  14:34            61.022 9-es2015.70ca7e368f9a76b59964.js
14.11.2019  14:34            21.375 common-es2015.54e83193ca37b87dd09a.js
14.11.2019  14:33            21.375 common-es5.0235b6e6e922441d2689.js
14.11.2019  14:34             1.542 index.html
14.11.2019  14:34           672.175 main-es2015.3ef131ef7dbb2e06e7da.js
14.11.2019  14:33           766.803 main-es5.916e65e01e2ceefdf20d.js
14.11.2019  14:34             1.133 manifest.webmanifest
14.11.2019  14:34           140.542 ngsw-worker.js
14.11.2019  14:34            41.830 ngsw.json
14.11.2019  14:34            37.304 polyfills-es2015.b9d61c8420fecac73ade.js
14.11.2019  14:33           113.605 polyfills-es5.22fcc391de73fbc61a39.js
14.11.2019  14:34             2.338 runtime-es2015.bd8a6f434a70643bf820.js
14.11.2019  14:33             2.335 runtime-es5.89f6f6bdf6a1c8dfa33b.js
14.11.2019  14:34               519 safety-worker.js
14.11.2019  14:34           180.853 styles.a78279866f8ed644cc62.css
14.11.2019  14:34               519 worker-basic.min.js
              25 Datei(en),      2.519.617 Bytes

And for Version 9.0.0-rc.1
bundle_after9
With files sizes:

14.11.2019  14:44           114.857 1-es2015.2eab0115fa339bfffb8d.js
14.11.2019  14:44           123.984 1-es5.2eab0115fa339bfffb8d.js
14.11.2019  14:44             8.158 6-es2015.061ae9b8b08999f9dc7d.js
14.11.2019  14:44             8.695 6-es5.061ae9b8b08999f9dc7d.js
14.11.2019  14:44             2.583 7-es2015.480bd5ab1960564ac076.js
14.11.2019  14:44             3.160 7-es5.480bd5ab1960564ac076.js
14.11.2019  14:45           100.520 8-es2015.b2e7b92e6a7ae57c60ad.js
14.11.2019  14:45           112.264 8-es5.b2e7b92e6a7ae57c60ad.js
14.11.2019  14:44            15.538 9-es2015.874764ed7c67c1148fb4.js
14.11.2019  14:44            16.387 9-es5.874764ed7c67c1148fb4.js
14.11.2019  14:45             1.560 index.html
14.11.2019  14:45           756.609 main-es2015.ba08a7a566bbc3160430.js
14.11.2019  14:45           890.459 main-es5.ba08a7a566bbc3160430.js
02.10.2019  10:25             1.133 manifest.webmanifest
14.11.2019  14:45           141.320 ngsw-worker.js
14.11.2019  14:45            41.662 ngsw.json
14.11.2019  14:44            36.808 polyfills-es2015.d339c9d166f5d5423290.js
14.11.2019  14:45           127.945 polyfills-es5.7a437adb78ef54de54d0.js
14.11.2019  14:44             2.389 runtime-es2015.62474928f6359459e271.js
14.11.2019  14:44             2.386 runtime-es5.62474928f6359459e271.js
14.11.2019  14:45               519 safety-worker.js
14.11.2019  14:44           190.800 styles.2d23fff4184672f07cf1.css
14.11.2019  14:45               519 worker-basic.min.js
              23 Datei(en),      2.700.258 Bytes

There are less files in Version 9.0.0-rc.1, but the overall file size is increased. I didn't update material, so there shouldn't been any restructuring.
Here is my package.json:

{
  "name": "frontend",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
    "start": "ng serve",
    "build": "ng build",
    "build:stats": "ng build --stats-json --namedChunks=true --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "analyze": "webpack-bundle-analyzer dist/stats-es2015.json"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~9.0.0-rc.1",
    "@angular/cdk": "~8.2.3",
    "@angular/common": "~9.0.0-rc.1",
    "@angular/compiler": "~9.0.0-rc.1",
    "@angular/core": "~9.0.0-rc.1",
    "@angular/forms": "~9.0.0-rc.1",
    "@angular/material": "^8.2.3",
    "@angular/platform-browser": "~9.0.0-rc.1",
    "@angular/platform-browser-dynamic": "~9.0.0-rc.1",
    "@angular/router": "~9.0.0-rc.1",
    "@angular/service-worker": "~9.0.0-rc.1",
    "@auth0/angular-jwt": "^3.0.0",
    "@ngrx/effects": "^8.5.0",
    "@ngrx/entity": "^8.5.0",
    "@ngrx/router-store": "^8.5.0",
    "@ngrx/schematics": "^8.5.0",
    "@ngrx/store": "^8.5.0",
    "@ngrx/store-devtools": "^8.5.0",
    "date-fns": "^2.6.0",
    "hammerjs": "^2.0.8",
    "rxjs": "~6.5.3",
    "tslib": "^1.10.0",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.900.0-rc.1",
    "@angular/cli": "^9.0.0-rc.1",
    "@angular/compiler-cli": "~9.0.0-rc.1",
    "@angular/language-service": "~9.0.0-rc.1",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.11.1",
    "codelyzer": "^5.1.2",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.6.4"
  }
}

While 8.2.x of Angular Material and the CDK may not completely break when used with Angular 9.0.0-rc.1, this is not a supported configuration. Please update Angular Material and the CDK to the latest RC (9.0.0-rc.1). There were quite a number of changes to better support Ivy in the 9.0.0-x releases of Angular Material/CDK that did not go into the 8.2.x releases.

It looks like @parys is the only one who has reported an issue with a supported configuration. Is it possible that you can post a GitHub repo with a reproduction?

@Splaktar my setup is based on angular material 9.0.0-x. angular 9.0.0-x and angular-cli 9.0.0-x @filipesilva how is this possible without any code changes, just run material schematics update where the import path of the modules changed.

@Splaktar Right now have no time. but you can look at my repo mylfc. in master is the latest stable angular. At brunch poc/angular9 updated
here is my app branches https://github.com/parys/MyLFC/tree/master/src/MyLiverpool.Web.WebApiNext/ClientApp
https://github.com/parys/MyLFC/tree/poc/angular9/src/MyLiverpool.Web.WebApiNext/ClientApp
if it's not working solution for you please notify me and when I will have some time will post repo with only angular apps without dotnet stuff :)

@parys based on the commit, did you use ng update @angular/material @angular/cdk --next to do this update? It doesn't look like the ng update schematics ran.

Can you try running the update schematics manually via ng update @angular/material --migrate-only --from 8 --to 9?

@elvirdolic did you run this manually for your project before posting those size results?

This issue was fixed in PR https://github.com/angular/components/pull/17704 today. We're hoping to get out an rc.2 ASAP so that these migrations can get more testing.

@Splaktar yes i run the schematics manually with 鈥攎igrate-only flag when I realized that it didn鈥檛 worked by default. We have a quiet big monorepo running this manually would have take too much time. I experienced that some files were not changed and I changed the import paths manually.

@elvirdolic if you have any info on why certain files might have been missed, please open up an issue for that in the Angular Components repo.

@parys thank you for the repro! This are the steps I followed to use it:

 git clone https://github.com/parys/MyLFC/
cd MyLFC/src/MyLiverpool.Web.WebApiNext/ClientApp/
yarn
yarn build --output-path dist/ivy

This got me an Ivy build in dist/ivy.

Then I added "angularCompilerOptions": { "enableIvy": false } to src/tsconfig.app.json and ran yarn build --output-path dist/ve to get a View Engine (non-Ivy) build in dist/ve.

To calculate the total size for es2015 bundles in Ivy and VE I used the following commands:

$ du -ach --max-depth=1 dist/ivy/*-es2015*.js
20K     dist/ivy/10-es2015.66f1eb6cd644d67f15ac.js
40K     dist/ivy/11-es2015.0b25ed23683e63edae11.js
24K     dist/ivy/12-es2015.3a06345c9606b3cb8362.js
20K     dist/ivy/13-es2015.8b5a695de5cd68380ac6.js
36K     dist/ivy/14-es2015.45a5aaca76e3deccec4e.js
12K     dist/ivy/15-es2015.13c04e1a0449697b4370.js
16K     dist/ivy/1-es2015.04de2045cc179e64f670.js 
724K    dist/ivy/20-es2015.d70ba6b906a9303a4044.js
20K     dist/ivy/21-es2015.1ae7bc356bff8b92250c.js
8.0K    dist/ivy/22-es2015.97b04a055b5f962e0309.js
20K     dist/ivy/23-es2015.f48ce3c563c84873e4ce.js
20K     dist/ivy/24-es2015.b58ac4301d3a403ff8c4.js
16K     dist/ivy/25-es2015.f779726ae819aa9739f6.js
36K     dist/ivy/26-es2015.01ae8157f3f380909ea2.js
28K     dist/ivy/27-es2015.a5154c0aee44ea51e928.js
52K     dist/ivy/28-es2015.fa9edb5e0d2684ee21ee.js
52K     dist/ivy/29-es2015.18d6fa26c54cd9313e7c.js
32K     dist/ivy/2-es2015.59540042273d26a17f33.js 
4.0K    dist/ivy/30-es2015.d5b3aae4d15fbad1c629.js
8.0K    dist/ivy/31-es2015.121dfca27ac90849a097.js
8.0K    dist/ivy/32-es2015.51a965f1045bc0e40032.js
8.0K    dist/ivy/33-es2015.e49cdd59d097a7be93ce.js
88K     dist/ivy/34-es2015.b7bb306b334a8dff2b75.js
16K     dist/ivy/35-es2015.a9b5ea4321a9fa57bcdb.js
8.0K    dist/ivy/36-es2015.7ba869149da02b085bf4.js
16K     dist/ivy/37-es2015.ef6457b56281155d3388.js
8.0K    dist/ivy/38-es2015.40bd972c02184a18eef1.js
4.0K    dist/ivy/39-es2015.b047fd864a142314cfd6.js
16K     dist/ivy/3-es2015.cbe344149634737fc684.js 
12K     dist/ivy/40-es2015.8c9381dc0ec0f646cd24.js       
8.0K    dist/ivy/41-es2015.d847d183be44b05fa266.js       
36K     dist/ivy/42-es2015.96223f01291c211cd4ca.js       
12K     dist/ivy/43-es2015.b62260233be0d6f8d472.js       
20K     dist/ivy/44-es2015.1220c9a4e782b623849e.js       
28K     dist/ivy/4-es2015.b842266e2f6893b4dc3c.js        
16K     dist/ivy/5-es2015.084e91003abe78df367e.js        
64K     dist/ivy/6-es2015.472e4ff75cdfb2259d88.js        
20K     dist/ivy/8-es2015.160eead525971e57ec5d.js        
48K     dist/ivy/9-es2015.e1f46ad685ec827665c5.js        
24K     dist/ivy/common-es2015.1cc936be97b3297c7814.js   
1.2M    dist/ivy/main-es2015.d29f2ce5e2c36181e304.js     
1.0K    dist/ivy/polyfills-es2015.f3658a1e714c1192c0ee.js
4.0K    dist/ivy/runtime-es2015.0e22c5d5d399b8826217.js  
2.8M    total
$ du -ach --max-depth=1 dist/ve/*-es2015*.js
16K     dist/ve/11-es2015.436fd4d1bf6c2f0af9f9.js
16K     dist/ve/12-es2015.ea4b19038c80d89e69ce.js
32K     dist/ve/13-es2015.efc10db2fca227bbe236.js
60K     dist/ve/14-es2015.91df8614ec1caae43957.js
40K     dist/ve/15-es2015.2990bb15dfa6ce50f91a.js
20K     dist/ve/16-es2015.e0377ebbf3e47c6176cf.js
24K     dist/ve/17-es2015.5ec1d39a99d8da6bcc2c.js
56K     dist/ve/18-es2015.6bf738114da2a757bfbf.js
48K     dist/ve/19-es2015.4975181c69a937241848.js
12K     dist/ve/1-es2015.e2edbcc2ba94d3eeb590.js
724K    dist/ve/24-es2015.a54325b4608772fa6a63.js
60K     dist/ve/25-es2015.e4b5460568b28b30e4f6.js
56K     dist/ve/26-es2015.b3a858106e8843e08d0a.js
44K     dist/ve/27-es2015.75f511dd04669a4298f5.js
60K     dist/ve/28-es2015.9fbb6faf061afb5906da.js
140K    dist/ve/29-es2015.acbf6174001c8fadef14.js
28K     dist/ve/2-es2015.d988558ff162cb002673.js
12K     dist/ve/30-es2015.31b863d3559f50aacc60.js
144K    dist/ve/31-es2015.3e5f8dea4af5ba9f5e08.js
32K     dist/ve/32-es2015.182bc0c78723ed7d0b05.js
48K     dist/ve/33-es2015.6f3cc3b8b1b9be1ab8a2.js
80K     dist/ve/34-es2015.9ee5d6fb696cc566e922.js
52K     dist/ve/35-es2015.1940a4b6b9de5becc968.js
80K     dist/ve/36-es2015.4e2148a4004807778a4e.js
20K     dist/ve/37-es2015.bb2a2644e78a5ad4b82e.js
12K     dist/ve/38-es2015.9081e5668f1f357b5284.js
16K     dist/ve/39-es2015.17e16102a3aefbc4e3fe.js
4.0K    dist/ve/3-es2015.5e726b861d01ef28e2b0.js
172K    dist/ve/40-es2015.f2a15b2740f8fdc676b7.js
12K     dist/ve/41-es2015.6a33304b86fe61a6c7bf.js
64K     dist/ve/42-es2015.577ec06bbd3d89f34bdb.js
44K     dist/ve/43-es2015.d1a8977b4782c345b13e.js
8.0K    dist/ve/44-es2015.520a10ae0e9eaa3125dd.js
28K     dist/ve/45-es2015.0a07fd9a79e4bc0ebecc.js
12K     dist/ve/46-es2015.a4ab78d3fffea4014f81.js
16K     dist/ve/47-es2015.d6b5df0f892830a121d6.js
44K     dist/ve/48-es2015.2f5b1e959ada640f93e3.js
24K     dist/ve/4-es2015.f151106c7ac588e420d4.js
20K     dist/ve/5-es2015.79b722303bba5036b8ec.js
16K     dist/ve/6-es2015.5302d2088d0cc1cb1b35.js
32K     dist/ve/7-es2015.c83e7bb4e8f7349d9f16.js
12K     dist/ve/8-es2015.5a2be8ff289c8acaad71.js
56K     dist/ve/9-es2015.6b38148134b0713697bc.js
28K     dist/ve/common-es2015.d8f40e56a357ada60df5.js
1.1M    dist/ve/main-es2015.cc5edbff7dd51d002dd6.js
1.0K    dist/ve/polyfills-es2015.b998c65176a95cc7e582.js
4.0K    dist/ve/runtime-es2015.c3b1be294314c03cc5e5.js
3.6M    total

I can verify exactly what you described: Ivy is a total of 2.8M while VE is 3.6M. Ivy did have a slightly bigger (1.2 vs 1.1M) main bundle.

I chalk this one off as a victory for Ivy though. It's a huge size savings!

@elvirdolic well anytime that dependencies are updated, the total application code changes. In version 9 we also switch projects over to using Ivy so that might change things further.

@elvirdolic @wambowams if you can provide me access to the project I can investigate your cases directly. Making a private github project (which is free now) and inviting me to it might be the best way to do it without exposing client projects to the web.

@filipesilva I'm not allowed to share the projects source to anyone outside our company. But thank u for the offer. I will update material and will take more attention to file size over the next releases.

Also tested in https://github.com/filipesilva/dashboard/tree/fs-test-setup, saw similar results:

git clone https://github.com/filipesilva/dashboard
cd dashboard
git checkout fs-test-setup
ng build --aot --prod --output-path .tmp/frontend-ivy

Then added "angularCompilerOptions": { "enableIvy": true } to ./tsconfig.json and ran ng build --aot --prod --output-path .tmp/frontend-ve.

Results for Ivy:

$ du -ach --max-depth=1 .tmp/frontend-ivy/*-es2015*.js
84K     .tmp/frontend-ivy/10-es2015.6f4faffc959274090f9d.js
4.0K    .tmp/frontend-ivy/11-es2015.36e2a5fbce179fd6bfc9.js       
24K     .tmp/frontend-ivy/12-es2015.9b8e8cbc45fb70045aa6.js       
8.0K    .tmp/frontend-ivy/13-es2015.8639ada61223297036c8.js       
4.0K    .tmp/frontend-ivy/14-es2015.70bfa728e6305f3bf49e.js       
4.0K    .tmp/frontend-ivy/15-es2015.bc088c4a5545dcdcdd78.js       
4.0K    .tmp/frontend-ivy/16-es2015.6e4f169c715a26581157.js       
8.0K    .tmp/frontend-ivy/17-es2015.2a6415476a0b3f4076de.js       
24K     .tmp/frontend-ivy/18-es2015.33c1cd5b43c5e5a4475f.js       
44K     .tmp/frontend-ivy/19-es2015.280da7eef72991032bd1.js       
8.0K    .tmp/frontend-ivy/20-es2015.9f2a79d2e1f6ff5c506b.js       
4.0K    .tmp/frontend-ivy/21-es2015.a07a4e7987898007cc72.js       
4.0K    .tmp/frontend-ivy/22-es2015.26904a217348123894c5.js       
8.0K    .tmp/frontend-ivy/23-es2015.275c85d6360c502f2fa4.js       
8.0K    .tmp/frontend-ivy/24-es2015.a7ce1269de98e2955334.js       
4.0K    .tmp/frontend-ivy/25-es2015.4716298640d1daf5f3a3.js       
8.0K    .tmp/frontend-ivy/26-es2015.dafbd4a668674aa47863.js       
12K     .tmp/frontend-ivy/27-es2015.42f39267584cfb03ca52.js       
8.0K    .tmp/frontend-ivy/28-es2015.d21fec7c8cc95ec2663d.js       
28K     .tmp/frontend-ivy/29-es2015.8bbe941fa856fc584884.js       
8.0K    .tmp/frontend-ivy/30-es2015.67e6fd53db209e6ea7fc.js
4.0K    .tmp/frontend-ivy/31-es2015.61dca86dd294dbaf83a4.js
4.0K    .tmp/frontend-ivy/32-es2015.85b68b349981b420df3b.js
8.0K    .tmp/frontend-ivy/33-es2015.54878e9f4ae10f91bc30.js
8.0K    .tmp/frontend-ivy/34-es2015.e4d2eddedde5e6eecb03.js
8.0K    .tmp/frontend-ivy/35-es2015.55e8d32e09baaba8ec65.js
8.0K    .tmp/frontend-ivy/36-es2015.badfb43e0452e0f43b1a.js
16K     .tmp/frontend-ivy/37-es2015.9bb7c75f9e9a7942e68e.js
912K    .tmp/frontend-ivy/6-es2015.400ab530e682280da428.js
296K    .tmp/frontend-ivy/7-es2015.c3abaeeaf0ea9a989a35.js
4.0K    .tmp/frontend-ivy/8-es2015.2133ac2e9d31e5a424df.js
8.0K    .tmp/frontend-ivy/9-es2015.e3edc7a4b5ac37af2517.js
8.0K    .tmp/frontend-ivy/common-es2015.f0d55a8f5317ef021c24.js
2.8M    .tmp/frontend-ivy/main-es2015.0962b245b0131a5cabfe.js
244K    .tmp/frontend-ivy/polyfills-es2015.6a454b045944ad8d7d8c.js
4.0K    .tmp/frontend-ivy/runtime-es2015.7acb8940f2851ad947b9.js
4.6M    total

Results for VE:

$ du -ach --max-depth=1 .tmp/frontend-ve/*-es2015*.js
36K     .tmp/frontend-ve/0-es2015.e4256bca91e561a798c7.js
24K     .tmp/frontend-ve/10-es2015.e5cc1cc1ac5e89f00d91.js
24K     .tmp/frontend-ve/11-es2015.f069791b41a7d1e72d5d.js
28K     .tmp/frontend-ve/12-es2015.f87dedc9b2fcebd18256.js
24K     .tmp/frontend-ve/13-es2015.691cc2d08c3e226282ae.js
24K     .tmp/frontend-ve/14-es2015.f216a2fd6729f21077fd.js
24K     .tmp/frontend-ve/15-es2015.c3149a6c08a82ccba9f9.js
28K     .tmp/frontend-ve/16-es2015.51946c2f1e72245cba88.js
36K     .tmp/frontend-ve/17-es2015.7db47689cb2447696f72.js
28K     .tmp/frontend-ve/18-es2015.64361d5edfec9245b9d2.js
956K    .tmp/frontend-ve/23-es2015.5929526f56da02d65a16.js
304K    .tmp/frontend-ve/24-es2015.b4a19b70f21c2bcd86b1.js
64K     .tmp/frontend-ve/25-es2015.989224f1711be675ee6b.js
24K     .tmp/frontend-ve/26-es2015.220ed13d3063a7751d97.js
24K     .tmp/frontend-ve/27-es2015.f2624779fbf2cb7dd813.js
24K     .tmp/frontend-ve/28-es2015.47591e91edce9108a6d2.js
28K     .tmp/frontend-ve/29-es2015.06ba400c25ac8ecdee2d.js
28K     .tmp/frontend-ve/2-es2015.9fe152b7d311f1004f99.js
12K     .tmp/frontend-ve/30-es2015.63c7b43e47089586348e.js
16K     .tmp/frontend-ve/31-es2015.1763aaf7613bea58fe3f.js
16K     .tmp/frontend-ve/32-es2015.39ca431400ad57b2f3c5.js
16K     .tmp/frontend-ve/33-es2015.c2ae0a81480dc71b4d54.js
56K     .tmp/frontend-ve/34-es2015.d9bc6baa860029a09fd4.js
52K     .tmp/frontend-ve/35-es2015.db589a866c1b0de7941f.js
48K     .tmp/frontend-ve/36-es2015.28117d37693349967fc4.js
44K     .tmp/frontend-ve/37-es2015.64bce3137e34d1aea5a5.js
36K     .tmp/frontend-ve/38-es2015.1aa7edc62f7129642a74.js
12K     .tmp/frontend-ve/39-es2015.db40c0fb19b09b1b4987.js
32K     .tmp/frontend-ve/3-es2015.c27b3e5b22adf3875951.js
244K    .tmp/frontend-ve/40-es2015.231868e7f3a6408fcf0e.js
8.0K    .tmp/frontend-ve/41-es2015.cb621a79c5f23050fa15.js
16K     .tmp/frontend-ve/42-es2015.15e0f6ab22bbfebb9193.js
24K     .tmp/frontend-ve/43-es2015.e4fa9864b0083db8da9f.js
24K     .tmp/frontend-ve/44-es2015.369f328dbc5f27425237.js
56K     .tmp/frontend-ve/45-es2015.9f70696bd83da69f0a4e.js
12K     .tmp/frontend-ve/46-es2015.d5776ab51b0fe3e26e3d.js
12K     .tmp/frontend-ve/47-es2015.dfd8a4a7ae5e37c2cee6.js
12K     .tmp/frontend-ve/48-es2015.172f4ff8f25f95d5e00c.js
12K     .tmp/frontend-ve/49-es2015.150feab69762047b9c55.js
28K     .tmp/frontend-ve/4-es2015.924d3a80380434584f6b.js
12K     .tmp/frontend-ve/50-es2015.1f2c1910ea031d2bc2fa.js
36K     .tmp/frontend-ve/51-es2015.57b6adadecf6a8869c21.js
16K     .tmp/frontend-ve/52-es2015.b8a4f08177353b549fbf.js
12K     .tmp/frontend-ve/53-es2015.0d760a8a3e46b448ebf6.js
52K     .tmp/frontend-ve/54-es2015.9a07263447328a36ea91.js
20K     .tmp/frontend-ve/5-es2015.e7bad09ebf8e7cd0dbcd.js
24K     .tmp/frontend-ve/6-es2015.0f52b4f6965eee9f304d.js
24K     .tmp/frontend-ve/7-es2015.c09718e920fffb41e0e4.js
24K     .tmp/frontend-ve/9-es2015.2f58b030f7d7d0f790ed.js
104K    .tmp/frontend-ve/common-es2015.41220724183fa89ec8e7.js
2.4M    .tmp/frontend-ve/main-es2015.106c70147daa70fbdc57.js
244K    .tmp/frontend-ve/polyfills-es2015.ba7bef98fffd1787a63b.js
4.0K    .tmp/frontend-ve/runtime-es2015.401816b1200374d9c8f6.js
5.4M    total

Similar thing happens: Ivy is a total of 4.6M while VE is 5.4M. Ivy has a bigger main at 2.8M while VE is only 2.4M.

@filipesilva I can see also a total size reduction but main.js is bigger. I personally thought main.js will be smaller because this has to be loaded upfront and I can鈥檛 work much around besides removing libs. But now we got 200-300kb additional into our main bundle by only updating to version 9. We worked hard to reduce main bundle size and now angular itself pushed 300kb into the main bundle. All these size reduction showcases are just for hello world apps but invalid for real world apps. I think this issue can be closed as this behavior seems to be expected. It should be pinned as many people will realize this and will ask why.

@elvirdolic keep in mind that if Ivy isn't working well for you, you can disable as described in https://next.angular.io/guide/ivy#opting-out-of-ivy-in-version-9. That should get you back to View Engine, which was the default in version 8.

@filipesilva yes, Ivy significantly decrease size. Maybe that's why material components grows so significantly to increase main bundle size.

@Splaktar tired to migrate with ng update and with --migrate only too. Both fails for me. but ng update for core and cli at my project fails at some migration step too...

@parys thank you for the reproduction. I was able to create 2 issues based on it

I've looked into this and have arrived at a minimal reproduction and explanation in https://github.com/filipesilva/factory-code-location


Factory code location differences between Ivy and View Engine

This application contains the following structure:

  • a shared ngmodule called SharedModule containing a component called SharedComponent
  • two lazy routes (lazy-one and lazy-two) that import SharedModule and use SharedComponent
  • a root AppModule that also imports SharedModule
  • SharedComponent is not used by any template outside the lazy routes

You can change between Ivy and VE by flipping the "enableIvy": true, switch in tsconfig.json.

Running ng build --prod with Ivy shows the following bundles:

chunk {0} runtime.js, runtime.js.map (runtime) 2.23 kB [entry] [rendered]
chunk {1} main.js, main.js.map (main) 262 kB [initial] [rendered]
chunk {2} polyfills.js, polyfills.js.map (polyfills) 36 kB [initial] [rendered]
chunk {3} styles.css, styles.css.map (styles) 38 bytes [initial] [rendered]
chunk {4} 4.js, 4.js.map () 852 bytes  [rendered]
chunk {5} 5.js, 5.js.map () 852 bytes  [rendered]
Date: 2019-11-22T14:22:51.177Z - Hash: f09b623e86f3d80f4000 - Time: 33745ms

Running ng build --prod with VE shows the following bundles:

chunk {0} runtime.js, runtime.js.map (runtime) 2.24 kB [entry] [rendered]
chunk {1} common.js, common.js.map (common) 530 bytes  [rendered]
chunk {2} main.js, main.js.map (main) 263 kB [initial] [rendered]
chunk {3} polyfills.js, polyfills.js.map (polyfills) 36 kB [initial] [rendered]
chunk {4} styles.css, styles.css.map (styles) 38 bytes [initial] [rendered]
chunk {5} 5.js, 5.js.map () 1.17 kB  [rendered]
chunk {6} 6.js, 6.js.map () 1.17 kB  [rendered]
Date: 2019-11-22T14:23:43.985Z - Hash: 90d6a92b420719382f49 - Time: 30408ms

The main difference is the common.js bundle.
In VE this bundle contains the template factory for SharedComponent ("shared-component works!").
In Ivy this template factory is in main.js instead.

However, Ivy will also have the common.js If you remove the SharedModule import from AppModule.

What's happening

To understand what's happening it is important to understand one important difference between Ivy and VE:

  • in VE, compiling a component would generate two files: the original transpiled file and a .ngfactory.js file containing the template factory
  • in Ivy, compiling a component generates only the original transpiled file

In this example SharedComponent is imported via SharedModule both through static imports in AppModule and in the lazy loaded modules.
However, it is not used in any templates outside of lazy routes.

This leads to the following difference between Ivy and VE:

  • in VE, the original SharedComponent class is in main.js but the .ngfactory.js file is in common.js
  • in Ivy, there is only the original SharedComponent class in main.js

If you remove the static import for SharedModule in AppModule there is no longer a chain of static imports in the main module for SharedComponent so it will always be in common.js, even in Ivy.

What you can do right now

If you find that with Ivy your main bundle gets larger while the other bundles get smaller, verify that the components and ngmodules that you want to be lazy loaded are only imported in lazy modules.
Anything that you import outside lazy modules can end up in the main bundle.

Meanwhile we'll try to figure out if there's something we can do in Angular itself to help.

Hi @filipesilva

thanks a lot for your response

I think it is not only related to the share module, in the source-map I gave in https://github.com/angular/angular/issues/33881#issuecomment-556970609 almost every module's bundle size has increased(including @angular/core and @angular/common)

@vthinkxie there is indeed another case, where Angular third party libraries go up in size, that I'm still looking into. We have a good reproduction for that one that I'm analysing in https://github.com/filipesilva/aio-ivy-ve/tree/master/aio.

@filipesilva The factory location example is interesting.

If compiled in Ivy production AOT, without enabling the option to keep the JIT metadata around, then nothing in main.js should reference the shared component, which would make it conceptually eligible to be moved to the common chunk. I downloaded your factory location reproducer repo, and checked the output. Sure enough, nothing in main.js does reference the shared component class, except itself.

The problem is that although everything in the shared.component.ts module is pure, and gets marked as such by buildOptimizer, webpack does not know realize the shared.component.ts module as a whole is side effect free, so it is unable to remove the import statement present in shared.module.ts, because it thinks there might be side effects. Therefore, when it decides what modules go in which chunks, it it needs to keep the shared-component.ts module in the main chunk too.

If webpack knew shared.component.ts was side effect free, it would have put the whole component in common.

Indeed if I roll back to using rc2, and add inform webpack that typescript files inside src/app don鈥檛 have side effects by adding the following as src/app/package.json then the component does end up in the common chunk, even though the app module imports the shared module:

{
  "sideEffects": [
    "**/*.css"
  ]
}

Now, why did I say roll back to rc2? Because starting in rc3, BuildOptimizer no longer removes ngSetModuleScope, relying on terser to remove it. Unfortunately terser runs after the chunks have been split, so webpack will think that shared.module.ts is using SharedComponent. This is due to PR #16228, obviously.

It would obviously be nice if there were some some webpack plugin that could attempt to automatically determine if a module has side effects, ideally taking the pure comments into account. Of course that is one of the major things rollup does that webpack does not.

@KevinCathcart you're quite right about everything you mentioned but it's important to note that application code being free from (toplevel) side effects is the main part here.

If app code is free from side effects then it becomes safe to elide imports that aren't used in the transpiled code. If it's not free from side effects, then it's not safe to elide.

Build Optimizer doesn't mark shared.component.ts module as pure in this case though. Rather, ngc compilation adds code to it that is marked as pure. But the ES module itself is not necessarily pure. Another PR referred to in the one you mentioned talks about this a bit more https://github.com/angular/angular/pull/33337#issuecomment-555948255.

After ascertaining an ES module is free from side effects, the bundler might be able to remove imports to it if no references are used. But we cannot assume app code is free from side effects.

The ngSetModuleScope change you mentioned is interesting. I removed it because https://github.com/angular/angular/pull/33671 instead gates it on a global flag called ngJitMode mode, and removes the pure comment. It still does no favors for webpack because webpack doesn't interpret the global value anyway. Come to think of it I don't think it does rollup any favors either though, since rollup itself doesn't have a way to define global values (I think?). But I think it's still correct because 傻傻setNgModuleScope being retained is only determined by that flag, not of optimizations being turned on.

That reproduction I put up was a bit simplistic though, and only focused on illustrating the exact minimal code to see the problem. If you're curious about the real case that led me there, it's https://github.com/kubernetes/dashboard. Here ComponentsModule imports SharedModule and both include a bunch of components and material modules. Then ComponentsModule is only used in lazy routes.... and on LoginModule, which isn't lazy and also uses a couple. So in this case some of the shared components are indeed used eagerly. I think it doesn't change much overall though.

Hi, @filipesilva
any update on this?
could this issue resolve before angular 9 final release?

Can the need: repro steps label be removed or are you still looking for reproductions?

@vthinkxie it's marked as a v9 blocker atm, so hopefully... @filipesilva is that milestone still correct?

We're just waiting on some styling compiler changes before reacessing, but we have been keeping track of how it changed over every RC.

Heya all,

We've been working on addressing the remaining size discrepancies and now believe that Ivy bundles should now in all common scenarios be either smaller or on par with View Engine (the previous compiler) bundles as of RC 11 of Angular Framework and CLI.

If you see your bundles increasing in size with Ivy, we want to know. Please open a new issue with a reproduction if possible. If you can鈥檛 publicly share your reproduction, consider sharing it privately instead.

Thanks to everyone that has spent time and energy producing the repos, they鈥檝e been invaluable tracking down these last bugs!

@filipesilva our main went from 2MB to 2.2MB after upgrade. Overall size reduction is massive but the main is bigger :). If you can guide me what do you need, I could help to create a reproduction.

Overall size reduction is massive but the main is bigger :)

I recently switched to the ES/ivy way of importing the lazy module and I noticed the same issue you described (main bundle got bigger, lazy bundle got smaller, so some code got moved to main bundle from the lazy one). But I got the expected behavior back by explicitly telling the webpack that I want _db-view module to be put into a separate chunk.

So if you load the lazy module using old/deprecated way, using NgModuleFactoryLoader.load("./_db-view/db-view.module#DbViewModule") + AngularCompilerPlugin.additionalLazyModules, then the entire lazy module gets automatically put into a separate chunk. But if you use ES importing you have to tweak the webpack's optimization.splitChunks config to get entire lazy module to be put in a separate chunk, at lest in my case.

Not sure this info will be helpful to someone since I don't use Angular CLI in the referenced project but use AngularCompilerPlugin directly.

@elvirdolic I'm currently following up on this in https://github.com/angular/angular-cli/issues/16866. If you could provide me with a repro, even privately, I could take a look.

@vladimiry I'm very interested in what you're doing! Can you give me a bit more context please? I didn't quite follow. An example would be super cool too!

@filipesilva, I just described the use of chunks splitting webpack's feature.

See 4 different output listings using angular v9.0.0:

  • ES import of _db-view module (notice _db-view.js has not been created, ie all the custom app code is put into single index.js chunk)
573964 index.js
4438375 vendor.js
4011 _db-view.js
577075 index.js
4438375 vendor.js
131362 _db-view.js
442694 index.js
4438375 vendor.js
133796 _db-view.js
442564 index.js
4438375 vendor.js

So if NgModuleFactoryLoader.load("./_db-view/db-view.module#DbViewModule") + AngularCompilerPlugin.additionalLazyModules used then there is no need to tweak the webpack config. But if you switch to the ES import the webpack config needs to be adjusted in order to put entire _db-view module/folder into a separate _db-view.js chunk.

@vladimiry not super sure what's happening there. That would make sense if you were not using Ivy though.

You had const {DbViewModule} = await import("src/web/browser-window/app/_db-view/db-view.module"); as the ES dynamic import. But with VE, we don't recognize this imports properly everywhere. In fact, we only recognize this format:

https://github.com/angular/angular-cli/blob/60918794287542b4f5aabfdf195089ea8d357f37/packages/ngtools/webpack/src/transformers/import_factory.ts#L107-L114

But I think you were using Ivy in your example? So that's really weird.

@filipesilva, the comment you referenced seems to be related to rewriting the Route definitions during the compiling stage. But I was describing the case of lazy-loading the Angular module without using the Route-thing, just dynamic lazy-loading the module from any point of the app.

But I think you were using Ivy in your example?

Yes, Ivy enabled there starting from some Angular v8 version.

Tried to reproduce by doing this:

  • ng new latest-app --routing && cd latest-app
  • ng generate module lazy --route lazy --module app.module
  • ng build --prod, saw a lazy chunk with 751 bytes
  • added "lazyModules": ["src/app/lazy/lazy.module.ts"]to my build options

    • this passes it on to AngularCompilerPlugin.additionalLazyModules

  • built again, saw a lazy chunk with 751 bytes
  • replaced the ES dynamic import with NgModuleFactoryLoader.load in src/app/app-routing.module.ts
const routes: Routes = [
  // { path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }
];
NgModuleFactoryLoader.load('./lazy/lazy.module#LazyModule');
  • built again, saw error:
ERROR in src/app/app-routing.module.ts:16:23 - error TS2339: Property 'load' does not exist on type 'typeof NgModuleFactoryLoader'.

16 NgModuleFactoryLoader.load('./lazy/lazy.module#LazyModule');
  • reverted the previous change, opened node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js and manually changed the cache groups
                cacheGroups: {
                    default: !!commonChunk && {
                        chunks: 'async',
                        minChunks: 2,
                        priority: 10,
                    },
                    common: !!commonChunk && {
                        name: 'common',
                        chunks: 'async',
                        minChunks: 2,
                        enforce: true,
                        priority: 5,
                    },
                    vendors: false,
                    vendor: !!vendorChunk && {
                        name: 'vendor',
                        chunks: 'initial',
                        enforce: true,
                        test: (module, chunks) => {
                            const moduleName = module.nameForCondition ? module.nameForCondition() : '';
                            return /[\\/]node_modules[\\/]/.test(moduleName)
                                && !chunks.some(({ name }) => utils_1.isPolyfillsEntry(name)
                                    || globalStylesBundleNames.includes(name));
                        },
                    },
                  "lazy": {
                    test: /src[\\/]app[\\/]lazy/,
                    name: "lazy",
                    chunks: "all",
                  },
                },
  • built again, saw a lazy chunk with 751 bytes

So I'm really confused by the behaviour you're seeing. I don't understand how you could use NgModuleFactoryLoader.load at all, and I can't imagine how these bundles could be any different when using Ivy.

Any chance you could give me a repro, or instructions on how to build your repo and what changes to make to see this behaviour?

I don't understand how you could use NgModuleFactoryLoader.load at all

I put it as NgModuleFactoryLoader.load in above messages for shortness. So the instance needs to be injected:

    constructor(
        private moduleLoader: NgModuleFactoryLoader,
    ) {}

And then you call it like on this line: const moduleFactory = await this.moduleLoader.load("./_db-view/db-view.module#DbViewModule");. On that commit on the left side is a use of old/deprecated way of lazy-loading the modules with NgModuleFactoryLoader, on the right side is the ES import + webpack optimization.splitChunks tweak.

or instructions on how to build your repo and what changes to make to see this behaviour

Repo is here https://github.com/vladimiry/ElectronMail/ To build Angular-related stuff you run yarn build:web:browser-window (the build outcome comes into ./app/web/browser-window/). The commit that switched the lazy module loading from using NgModuleFactoryLoader to regular ES import + webpack optimization.splitChunks tweak.

@vladimiry I did this:

  • git clone https://github.com/vladimiry/ElectronMail/ && cd ElectronMail
  • yarn
  • yarn build:web:browser-window && ls -la app/web/browser-window/
total 5109
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:05 .
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:05 ..
-rw-r--r-- 1 kamik 197609  131593 Feb 13 16:05 _db-view.js
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:05 assets
-rw-r--r-- 1 kamik 197609     833 Feb 13 16:05 index.css
-rw-r--r-- 1 kamik 197609     542 Feb 13 16:05 index.html
-rw-r--r-- 1 kamik 197609  443705 Feb 13 16:05 index.js
-rw-r--r-- 1 kamik 197609  195805 Feb 13 16:05 vendor.css
-rw-r--r-- 1 kamik 197609 4438375 Feb 13 16:05 vendor.js
  • edited webpack-configs/web/browser-window.ts to remove the cache group for dbview
                cacheGroups: {
                    commons: {
                        test: /[\\/]node_modules[\\/]|[\\/]vendor[\\/]/,
                        name: "vendor",
                        chunks: "all",
                    },
                    // "_db-view": {
                    //     test: /src[\\/]web[\\/]browser-window[\\/]app[\\/]_db-view/,
                    //     name: "_db-view",
                    //     chunks: "all",
                    // },
                },
  • rm -rf app/web/browser-window/ && yarn build:web:browser-window && ls -la app/web/browser-window/
total 5101
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:15 .
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:15 ..
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:15 assets
-rw-r--r-- 1 kamik 197609     833 Feb 13 16:15 index.css
-rw-r--r-- 1 kamik 197609     486 Feb 13 16:15 index.html
-rw-r--r-- 1 kamik 197609  574975 Feb 13 16:15 index.js
-rw-r--r-- 1 kamik 197609  195805 Feb 13 16:15 vendor.css
-rw-r--r-- 1 kamik 197609 4438375 Feb 13 16:15 vendor.js

So without the cacheGroups your build doesn't actually produce another chunk. This makes me think that _db-view ends up not being lazy loaded at all.

I checked your tsconfig and noticed you were using "module": "commonjs",. This causes typescript to not actually emit the ES import statements. I added "compilerOptions": { "module": "esnext", }, in src/web/browser-window/tsconfig.json (couldn't add it in the base tsconfig because you also use it for compiling the webpack configs) and built again:

  • rm -rf app/web/browser-window/ && yarn build:web:browser-window && ls -la app/web/browser-window/
total 5145
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:25 .
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:25 ..
-rw-r--r-- 1 kamik 197609  147580 Feb 13 16:25 0.js
-rw-r--r-- 1 kamik 197609  195806 Feb 13 16:25 1.js
-rw-r--r-- 1 kamik 197609   30699 Feb 13 16:25 2.js
-rw-r--r-- 1 kamik 197609  145509 Feb 13 16:25 3.js
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:25 assets
-rw-r--r-- 1 kamik 197609     833 Feb 13 16:25 index.css
-rw-r--r-- 1 kamik 197609     486 Feb 13 16:25 index.html
-rw-r--r-- 1 kamik 197609  144076 Feb 13 16:25 index.js
-rw-r--r-- 1 kamik 197609  195805 Feb 13 16:25 vendor.css
-rw-r--r-- 1 kamik 197609 4380827 Feb 13 16:25 vendor.js

Now we can actually see multiple separate chunks be created automatically (one for each lazy import) and a much smaller index.js.

You can even do better if you disable your vendor cacheGroups. That doesn't help at all because it prevents optimizations in the eagerly loaded chunks, and also eagerly loads dependencies for your lazy chunks.

total 5129
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:28 .
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:28 ..
-rw-r--r-- 1 kamik 197609  596791 Feb 13 16:28 0.js
-rw-r--r-- 1 kamik 197609  268453 Feb 13 16:28 1.js
-rw-r--r-- 1 kamik 197609  150874 Feb 13 16:28 2.js
-rw-r--r-- 1 kamik 197609  195806 Feb 13 16:28 3.js
-rw-r--r-- 1 kamik 197609   30699 Feb 13 16:28 4.js
-rw-r--r-- 1 kamik 197609  483746 Feb 13 16:28 5.js
-rw-r--r-- 1 kamik 197609  145509 Feb 13 16:28 6.js
drwxr-xr-x 1 kamik 197609       0 Feb 13 16:28 assets
-rw-r--r-- 1 kamik 197609  196638 Feb 13 16:28 index.css
-rw-r--r-- 1 kamik 197609     370 Feb 13 16:28 index.html
-rw-r--r-- 1 kamik 197609 3155513 Feb 13 16:28 index.js

This way you get a slightly smaller total payload, and a significantly smaller initial payload.

More to the point insofar as this issue is concerned though, the cache group doesn't do anything useful when you already have ES import() statements with Ivy.

@filipesilva, thanks for thorough analysis. So "compilerOptions": { "module": "esnext", } is the key thing here which I missed.

You can even do better if you disable your vendor cacheGroups. That doesn't help at all because it prevents optimizations in the eagerly loaded chunks, and also eagerly loads dependencies for your lazy chunks.

Yeah, that vendor cacheGroups added there initially and intentionally for my convenience (the css part of this bundle gets independently loaded by different windows, hacky stuff) but maybe it's time to reconsider and get rid of it even though that's a desktop app (the size is not so important, but code parsing still takes time same like in the regular browser). You also probably noticed that there is no any code minification applied, it's also by design as it's the desktop app, not the real web.

This was pretty frustrating when expecting the file size to improve. I have a fairly large app and the file size on main went up from 587K to 610K. I'm not even sure where to start looking for the issue.

"dependencies": {
"@angular/animations": "^9.0.2",
"@angular/common": "^9.0.2",
"@angular/compiler": "^9.0.2",
"@angular/core": "^9.0.2",
"@angular/forms": "^9.0.2",
"@angular/localize": "~9.0.2",
"@angular/material": "^9.0.0",
"@angular/platform-browser": "^9.0.2",
"@angular/platform-browser-dynamic": "^9.0.2",
"@angular/platform-server": "^9.0.2",
"@angular/router": "^9.0.2",
"@nguniversal/common": "^8.1.1",
"@nguniversal/express-engine": "^8.1.1",
"@nguniversal/module-map-ngfactory-loader": "^8.1.1",
"angular2-text-mask": "^9.0.0",
"core-js": "^2.6.5",
"custom-event-polyfill": "^1.0.7",
"fingerprintjs2": "^2.0.6",
"gridlex": "^2.7.1",
"http-proxy-middleware": "^0.18.0",
"immutable": "^3.8.2",
"jsvat": "^2.1.2",
"ngx-cookie-service": "^2.2.0",
"node-fetch": "^2.3.0",
"primeng": "9.0.0-rc.4",
"rxjs": "^6.5.3",
"sass-loader": "^7.1.0",
"ts-loader": "^4.5.0",
"tslib": "^1.10.0",
"url-search-params-polyfill": "^4.0.1",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.3",
"@angular/cdk": "^9.0.0",
"@angular/cli": "9.0.3",
"@angular/compiler-cli": "9.0.2",
"@angular/language-service": "9.0.2",
"@types/google.analytics": "0.0.39",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "^2.0.6",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"compression": "^1.7.4",
"cookie-parser": "^1.4.4",
"express": "^4.16.4",
"git-describe": "^4.0.4",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "^4.0.1",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.4.3",
"karma-jasmine": "^1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"nodemon": "^1.18.11",
"protractor": "^5.4.2",
"ts-node": "~3.2.0",
"tslint": "~5.11.0",
"typescript": "^3.7.5",
"webpack-cli": "^3.3.0"
}

@santhony7 can you check if your total app size, with the size of the lazy bundles included, went down?

@filipesilva got same problem, here is bundle-analyzer results
Before:
image
After:
image

I think this may be due to ngcc processing @angular/* packages, should ngcc do this?

Yes, ngcc should process those packages.

Webpack Bundle analyzer won't give the correct numbers in a lot of cases though. Using source-map-explorer will give you a more accurate picture of the code in your bundles, as described in https://angular.io/guide/deployment#inspect-the-bundles.

@filipesilva Yeah, I do know that the total size has been reduced but I also think it is important to be very specific about that when claiming file size reduction. As you know, the browser's time-to-interactive or the page-speed-index is pretty much the only metric that matters to leadership. Selling them the upgrade on the promise of faster (initial) download only to find out you made it worse can be a bit disheartening.

To the best of my knowledge, we only have one situation currently where the main bundle gets bigger: https://github.com/angular/angular-cli/issues/16866. Still trying to figure out what we can do in that case. Do you also use material in your app?

Yes. I use material, but it is not big part of my app. Thanks for your help.

@filipesilva Yes sir we do. "@angular/material": "^9.1.0",

Hi everyone! my main.js is considerably bigger with ivy. Any idea what it could be?

Before:
Captura de Pantalla 2020-03-26 a la(s) 12 32 09 p 聽m
After:
Captura de Pantalla 2020-03-26 a la(s) 12 31 55 p 聽m

@yamidvo the tool you are using (webpack-bundle-analyzer) will not provide very precise numbers with Angular CLI 9, because we process bundles after they leave webpack. A better tool to get that data would be source-map-explorer as described in https://angular.io/guide/deployment#inspect-the-bundles. That will give you very precise measurements.

2020-03-26_21-17-31
@filipesilva
Full bundle: 2.88 MB
Main bundle: 972, 9 KB (size in webpack-bundle-analyzer 973.6 KB (less than 1KB)
Angular modules: 704.45 KB + rxjs 48.26 + zone.js 35.7 = 788 KB is only for angular stuff. ( and I've already moved to lazy modules almost all material modules that not used at the index page. As I remember angular 9 decrease full size from 3.4 to 2.9 MB but increased main bundle a lot( and almost all is taken by angular(

Similarly, the difference is large

before:
Captura de Pantalla 2020-03-26 a la(s) 1 54 14 p 聽m

after:
Captura de Pantalla 2020-03-26 a la(s) 1 54 21 p 聽m

@parys I can't say much regarding that screenshot as I don't have anything to compare it to. Could you include a screenshot of the source map explorer output from before 9 please?

@yamidvo I noticed you have a large shared module, which can be a problem as described in https://github.com/angular/angular-cli/issues/16146#issuecomment-557559287. Have you tried what I mention in the "What you can do right now" section of that comment? You might have static imports to your shared code from your main entry point, causing it to not be loaded lazily at all. I don't think this is a problem we can fix on our side.

Either way, something else you two can do is to opt out of Ivy, as described in https://angular.io/guide/ivy#opting-out-of-ivy-in-version-9. This should get back to the setup used in Angular version 8.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings