You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HelloWorld retains too much code with build optimizer #21880
We can create hello world in with Ivy in both closure and rollup with build optimizer.
Closure: 8,152 bytes
Rollup with BO: 34,304 bytes
So it seems that BO is still retaining about 25kb of unneeded code. Need to investigate
rm -rf dist
./built.sh
cd integration/hello_world__render3__closure
yarn install
yarn test
ls -al dist/bundle.js
>>> -rw-r--r-- 1 misko eng 8152 Jan 29 15:44 dist/bundle.js
cd ../hello_world__render3__rollup/
yarn install
yarn test
ls -al dist/bundle.js
-rw-r--r-- 1 misko eng 34304 Jan 29 16:05 dist/bundle.js
There was also a matter of order of plugins. Rollup was showing this warning:
(!) Missing exports
https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module
commonjs-proxy:D:\work\angular\integration\hello_world__render3__rollup\node_modules\tslib\tslib.es6.js
default is not exported by node_modules\tslib\tslib.es6.js
1: import * as tslib_es6 from "D:\\work\\angular\\integration\\hello_world__render3__rollup\\node_modules\\tslib\\tslib.es6.js"; export default ( tslib_es6 && tslib_es6['default'] ) || tslib_
es6;
This happened due to Build Optimizer trying to alter the tslib imports I believe. Having it before the other plugins caused the whole of tslib to be retained.
Moving it to just before Uglify drops the size further down to 12k:
Some time ago I got a very insightful suggestion from @kzc about how using the uglify compress passes: 3 option can help in some code bases when using PURE comments (webpack/webpack#2899 (comment)).
Applying it in this example further shaves .5kb, resulting in a 9.5k bundle:
For reference, the closure example in my system results in a 9.7k bundle instead of 8.1k you see. I do not know why closure performs differently for me.
But within the same environment there seems to be parity in final size, with rollup+uglify performing slightly better (9.5k vs 9.7k).
Also try the unsafe compress option. It is safe for sane JS that does not alter built-in functions and classes. Uglify has to be overly conservative by default.
You might also try disabling keep_fargs to remove unused function parameters. It's fine for code that does not interrogate the Function's length to get the number of args:
Following your suggestions, unsafe: true, brought it down to 9.4k, while keep_fargs: false shaved off some 20 bytes. So final remained 9.4k. Not a big difference, but a difference nonetheless. For general purpose uglification I'd probably leave them off.
@filipesilva What you have is more or less optimal as far as uglify-es settings go.
You could save an additional 200 bytes by using rollup-plugin-uglify@2.0.1 which uses ES5-only uglify-js@3 instead of rollup-plugin-uglify@3 which uses ES6+ uglify-es@3. The difference is due to the fact that uglify-js has a rename pass that uglify-es lacks due to ES6+ implementation issues. The rename pass runs before compress and assigns every symbol a globally unique name which facilitates better function inlining.
Rollup has its own equivalent of pure_getters called treeshake.propertyReadSideEffects: false. Perhaps if it is set in the rollup phase prior to uglify it may result in a smaller bundle.
I wasn't aware of those options actually! Just tested them now, and neither pureExternalModules nor propertyReadSideEffects (nor both) reduce the output beyond the 9.5k threshold.
Also tried turning on pureExternalModules: true to see if it could do without Build Optimizer but it generated a 124k bundle instead.
If you study the uglify output and identify any low hanging fruit micro-optimizations that could be implemented, by all means create a new uglify issue detailing the optimization heuristic.
$ time yarn rollup
yarn run v1.3.2
$ rollup -c rollup.config.js
built/index.js → dist/bundle.js...
created dist/bundle.js in 6.7s
Done in 8.07s.
real 0m9.154s
user 0m0.060s
sys 0m0.136s
$ time yarn closure
yarn run v1.3.2
$ java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf
Done in 4.92s.
real 0m5.958s
user 0m0.061s
sys 0m0.075s
So rollup was 9s while closure was 6s.
I did have a look at the 9.7k closure bundle and something I noticed was property renaming. I think closure does that really well especially with the extra info in comments that angular packages have.
But I didn't look too deeply because those missing 1.6k still make me wonder a bit. I want to figure out if it's an env thing before I start looking too deeply into the final bundle code.
Also need to check with @mhevery if I can configure closure to do the equivalent of uglify mangle: false, output: { beautify: true },, which makes reading the final bundles much nicer.
Closure's java build time is very good. Much better than I remember. Is google-closure-compiler-js comparable for those who want to avoid installing the JRE?
As far as property mangling goes, uglify also supports this:
It can reduce minified output by 40% in some cases. It optionally supports property whitelists and regex patterns as well as quoted property exclusion. Uglify property mangling is discouraged for general use but is fine for bundling a specific library or application where source rules governing properties are strictly enforced.
@filipesilva this is awesome investigation. You rock! Let me us internalize this and we will get back to you. No more action is needed from your side for now.
@kzc unfortunately the JS version of closure compiler uses Google Web Toolkit to transpile from Java, and it's bad. The result consumes a lot of memory, and doesn't have feature parity. For example, it doesn't support unicode so no Angular app could ever use it (we use the lower barred latin o character for "private namespacing")
The right sol'n is just to statically link an openjdk into the regular NPM distro of closure compiler so you aren't aware of the Java runtime dependency. I tried to tell them but sadly not everyone listens to me.
Whether it works in this case without a mangle property whitelist is another matter - probably it wouldn't. The command above excludes mangling built-in DOM props as well as any property that is quoted at least once in the input (i.e.: ({"foo": 1}) or obj["bar"]) - even in dead code.
Just run $ bazel build packages/core/test/bundle/hello_world:all
in dist/bin/packages/core/test/bundle/hello_world/ you'll find files that trace what happened:
_bundle.rollup.conf.js was the config passed to rollup --config. Rollup receives the application and rxjs as ES2015, and gets Angular with the rollup-plugin-node-resolve plugin which means we get ES5ESM. You can see that build-optimizer is included in here.
bundle.es6.js is the output from rollup
then we run it through tsc to downlevel, and get bundle.js
then we run it through purify, to get _bundle.post-purify.js
then we run it through uglify. _bundle.uglify.json was the config passed to uglify --config-file bundle.min.js is the final result.
Also, if you request the debug output: $ bazel build packages/core/test/bundle/hello_world:bundle.min_debug.js
it will use _bundle.debug.uglify.json and produce bundle.min_debug.js which is human-inspectable.
I'm still playing with options to get the small sizes you got - but we don't have a test here that the application still works, and I'd rather know when the options are too aggressive.
@kzc going down to 7.5k with property mangling is pretty impressive. I tried it once yesterday but for some reason didn't get much size reduction. I probably messed up the config at the time.
@alexeagle you mention that you run tsc from bundle.es6.js to get the ES5 bundle.js. But remember that TS is stripping the PURE comments (see angular/devkit#412). Also Build Optimizer might not detect some of the ES2015 input, the most aggressive optimizations expect ES5 patterns.
Purify is also not needed, as it only does anything at all to webpack bundles.
Okay, I updated the branch above to use ES5ESM inputs from the app instead. It's still not getting the same size, maybe because of these errors:
angular/packages/core/test/bundle/hello_world/index.js → bazel-out/k8-fastbuild/bin/packages/core/test/bundle/hello_world/bundle.js...
(!) `this` has been rewritten to `undefined`
https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined
../../../../execroot/angular/bazel-out/k8-fastbuild/bin/packages/core/core.es5_esm/angular/packages/core/src/metadata/di.js
6: * found in the LICENSE file at https://angular.io/license
7: */
8: var __assign = (this && this.__assign) || Object.assign || function (t) {
^
9: for (var s, i = 1, n = arguments.length; i < n; i++) {
10: s = arguments[i];
...and 1 other occurrence
note in our case, we are bundling from Angular sources, not from Angular's NPM distribution, because this is a functional test within the angular repo. That's the obvious difference from your repro.
Activity
filipesilva commentedon Jan 30, 2018
Looking into this, there is one important Uglify option we set in the CLI that is not set here:
compress.pure_getters = true.With it the rollup bundle goes down to 16k.
Looking further into it.
filipesilva commentedon Jan 30, 2018
There was also a matter of order of plugins. Rollup was showing this warning:
This happened due to Build Optimizer trying to alter the tslib imports I believe. Having it before the other plugins caused the whole of tslib to be retained.
Moving it to just before Uglify drops the size further down to 12k:
filipesilva commentedon Jan 30, 2018
I know resolution of
rxjsmodules is different between rollup and closure, so I tried to force usage of files inrxjs/_esm5.https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md#build-and-treeshaking mentions a way to do path mapping for Webpack, but I couldn't find any for rollup. I looked around a bit and found https://github.com/chjtx/rollup-plugin-paths, which allows path mapping in Rollup.
Using path mapping the bundle for drops to 10kb:
filipesilva commentedon Jan 30, 2018
Some time ago I got a very insightful suggestion from @kzc about how using the uglify compress
passes: 3option can help in some code bases when usingPUREcomments (webpack/webpack#2899 (comment)).Applying it in this example further shaves .5kb, resulting in a 9.5k bundle:
filipesilva commentedon Jan 30, 2018
For reference, the closure example in my system results in a 9.7k bundle instead of 8.1k you see. I do not know why closure performs differently for me.
But within the same environment there seems to be parity in final size, with rollup+uglify performing slightly better (9.5k vs 9.7k).
kzc commentedon Jan 30, 2018
Also try the
unsafecompress option. It is safe for sane JS that does not alter built-in functions and classes. Uglify has to be overly conservative by default.You might also try disabling
keep_fargsto remove unused function parameters. It's fine for code that does not interrogate the Function'slengthto get the number of args:If you post the uglify input file in a gist or equivalent perhaps I could offer other suggestions. I don't want to build the code.
filipesilva commentedon Jan 30, 2018
@kzc thanks for chiming in with some more suggestions!
For reference, here are the relevant files so far:
Following your suggestions,
unsafe: true,brought it down to 9.4k, whilekeep_fargs: falseshaved off some 20 bytes. So final remained 9.4k. Not a big difference, but a difference nonetheless. For general purpose uglification I'd probably leave them off.kzc commentedon Jan 30, 2018
@filipesilva What you have is more or less optimal as far as
uglify-essettings go.You could save an additional 200 bytes by using
rollup-plugin-uglify@2.0.1which uses ES5-onlyuglify-js@3instead ofrollup-plugin-uglify@3which uses ES6+uglify-es@3. The difference is due to the fact thatuglify-jshas arenamepass thatuglify-eslacks due to ES6+ implementation issues. Therenamepass runs beforecompressand assigns every symbol a globally unique name which facilitates better function inlining.filipesilva commentedon Jan 30, 2018
@kzc thanks for checking these results with me, it really helps to have the validation 👍
The current results I got were with
rollup-plugin-uglify@2.0.1actually. I tried3.0.0and exactly like you said there was roughly 200bytes extra.kzc commentedon Jan 30, 2018
Rollup has its own equivalent of
pure_getterscalledtreeshake.propertyReadSideEffects: false. Perhaps if it is set in the rollup phase prior to uglify it may result in a smaller bundle.filipesilva commentedon Jan 30, 2018
I wasn't aware of those options actually! Just tested them now, and neither
pureExternalModulesnorpropertyReadSideEffects(nor both) reduce the output beyond the 9.5k threshold.Also tried turning on
pureExternalModules: trueto see if it could do without Build Optimizer but it generated a 124k bundle instead.kzc commentedon Jan 30, 2018
Oh well - worth a shot.
If you study the uglify output and identify any low hanging fruit micro-optimizations that could be implemented, by all means create a new uglify issue detailing the optimization heuristic.
kzc commentedon Jan 30, 2018
Just curious - what was the total build time for closure's 8.1K (9.7k?) bundle compared to rollup+BO+uglify's 9.5K bundle?
filipesilva commentedon Jan 30, 2018
Here are the times on my machine:
So rollup was 9s while closure was 6s.
I did have a look at the 9.7k closure bundle and something I noticed was property renaming. I think closure does that really well especially with the extra info in comments that angular packages have.
But I didn't look too deeply because those missing 1.6k still make me wonder a bit. I want to figure out if it's an env thing before I start looking too deeply into the final bundle code.
Also need to check with @mhevery if I can configure closure to do the equivalent of uglify
mangle: false, output: { beautify: true },, which makes reading the final bundles much nicer.kzc commentedon Jan 30, 2018
Closure's java build time is very good. Much better than I remember. Is
google-closure-compiler-jscomparable for those who want to avoid installing the JRE?As far as property mangling goes, uglify also supports this:
https://github.com/mishoo/UglifyJS2#cli-mangling-property-names---mangle-props
https://github.com/mishoo/UglifyJS2#mangle-properties-options
It can reduce minified output by 40% in some cases. It optionally supports property whitelists and regex patterns as well as quoted property exclusion. Uglify property mangling is discouraged for general use but is fine for bundling a specific library or application where source rules governing properties are strictly enforced.
mhevery commentedon Jan 30, 2018
@filipesilva this is awesome investigation. You rock! Let me us internalize this and we will get back to you. No more action is needed from your side for now.
alexeagle commentedon Jan 30, 2018
@kzc unfortunately the JS version of closure compiler uses Google Web Toolkit to transpile from Java, and it's bad. The result consumes a lot of memory, and doesn't have feature parity. For example, it doesn't support unicode so no Angular app could ever use it (we use the lower barred latin o character for "private namespacing")
The right sol'n is just to statically link an openjdk into the regular NPM distro of closure compiler so you aren't aware of the Java runtime dependency. I tried to tell them but sadly not everyone listens to me.
kzc commentedon Jan 31, 2018
@alexeagle Thanks for pointing out the difference between the two Closure versions.
@filipesilva FWIW, here's the size of the uglified bundle with property mangling:
Whether it works in this case without a mangle property whitelist is another matter - probably it wouldn't. The command above excludes mangling built-in DOM props as well as any property that is quoted at least once in the input (i.e.:
({"foo": 1})orobj["bar"]) - even in dead code.alexeagle commentedon Jan 31, 2018
Hey @filipesilva
I have this somewhat in a real state, close to being ready to land in Angular:
https://github.com/alexeagle/angular/tree/build-optimizer
Just run
$ bazel build packages/core/test/bundle/hello_world:allin
dist/bin/packages/core/test/bundle/hello_world/you'll find files that trace what happened:_bundle.rollup.conf.jswas the config passed to rollup--config. Rollup receives the application and rxjs as ES2015, and gets Angular with therollup-plugin-node-resolveplugin which means we get ES5ESM. You can see that build-optimizer is included in here.bundle.es6.jsis the output from rollupthen we run it through
tscto downlevel, and getbundle.jsthen we run it through purify, to get
_bundle.post-purify.jsthen we run it through uglify.
_bundle.uglify.jsonwas the config passed to uglify--config-filebundle.min.jsis the final result.Also, if you request the debug output:
$ bazel build packages/core/test/bundle/hello_world:bundle.min_debug.jsit will use
_bundle.debug.uglify.jsonand producebundle.min_debug.jswhich is human-inspectable.I'm still playing with options to get the small sizes you got - but we don't have a test here that the application still works, and I'd rather know when the options are too aggressive.
filipesilva commentedon Jan 31, 2018
@kzc going down to 7.5k with property mangling is pretty impressive. I tried it once yesterday but for some reason didn't get much size reduction. I probably messed up the config at the time.
@alexeagle you mention that you run
tscfrombundle.es6.jsto get the ES5bundle.js. But remember that TS is stripping the PURE comments (see angular/devkit#412). Also Build Optimizer might not detect some of the ES2015 input, the most aggressive optimizations expect ES5 patterns.Purify is also not needed, as it only does anything at all to webpack bundles.
alexeagle commentedon Feb 1, 2018
Okay, I updated the branch above to use ES5ESM inputs from the app instead. It's still not getting the same size, maybe because of these errors:
note in our case, we are bundling from Angular sources, not from Angular's NPM distribution, because this is a functional test within the angular repo. That's the obvious difference from your repro.
IgorMinar commentedon Feb 9, 2018
is this still valid?
alexeagle commentedon Feb 9, 2018
We solved it, buildsize is keeping us honest at 8k
angular-automatic-lock-bot commentedon Sep 13, 2019
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.