Angular: Use Closure Compiler with offline template compiler

Created on 9 May 2016  ·  105Comments  ·  Source: angular/angular

Angular's new offline compiler was demo'ed at ng-conf day 2 keynote by @robwormald. It generates tree-shakable ES6 code. Four steps are then required:

  • run a tree-shaker to remove ES6 modules not reachable from the entry point, in our demo we showed rollup
  • run a bundler to reduce down the module imports into a declarations in a single file; we showed system.js
  • down-level to ES5 - we showed this with TypeScript
  • minify to shorten symbol names, we used uglify.

This produced a 49k JS file, but requires a lot of configuration.

Google's Closure Compiler (https://developers.google.com/closure/compiler/) produces very small JS. It does all four steps required above, so the configuration should be a lot simpler. We also suspect we can get a smaller binary size for ng2-hello-world, around 36k.

Wiring this up requires:

  • [x] add tsickle's closure annotation helper to `ngc
  • [x] modify Angular's ES6 distribution to be closure-compiler compatible
  • [x] ?? modify ES6 distro of our dependencies (rxjs, zone.js) to be closure-compiler compatible
  • [x] figure out a minimal build (maybe a shell script) that successfully compiles the application together with the framework and its dependencies
  • [x] document how we did it so others can repro
  • [x] bundle externs with Angular to allow Protractor testing (the BrowserNodeGlobal type)
packaging feature medium

Most helpful comment

Today we got a release of rxjs that works with closure compiler, and I've updated the example repo
https://github.com/alexeagle/closure-compiler-angular-bundling

The externs are cleaned up as well - Angular and Zone.js both distribute the needed externs in their respective packages.
We'll add some documentation and an announcement about the support shortly.

All 105 comments

The part I think I most need your guidance on is where/how this fits into the larger angular build process.
Do we want to (1) build angular itself as a minified external bundle? or (2) compile angular+user code together into a single bundle? In the latter case we'll need to integrate into gulp or whatever.

We do distribute Angular bundles, as ES5 with UMD module loader built-in.
However, it would be impossible to pick this back apart again to tree-shake
it, so our plan has been #2.
I'm not sure we should commit to building plugins for the several popular
userland build tools; others in the community will pick that up. I think a
simpler "npm run build && npm run closure-compiler" with shell scripts is
sufficient at this point.

On Mon, May 9, 2016 at 11:26 AM Evan Martin [email protected]
wrote:

The part I think I most need your guidance on is where/how this fits into
the larger angular build process.
Do we want to (1) build angular itself as a minified external bundle? or
(2) compile angular+user code together into a single bundle? In the latter
case we'll need to integrate into gulp or whatever.


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-217947352

FYI @jeffbcross maybe this can make your PWA demo for I/O

Yeah that would be awesome if we could have this working by the end of this week.

Some notes from the hacks I've had to make so far, in the hopes that others can reproduce:

Closure Compiler doesn't handle all ES6 module syntax, and discourages using it. So our strategy so far has been to use goog.module syntax. That's not a --module option to tsc, so we use tsickle to re-write it. That means angular and its dependencies have to be emitted as "Closure ES6".

Need to build closure compiler from HEAD, see https://github.com/google/closure-compiler/blob/master/README.md#building-it-yourself

To build angular into closure ES6, I have local edits in the ngc tool to run tsickle's closure annotation and convertCommonJsToGoogModule. See https://github.com/alexeagle/angular/tree/closure_hack2
Build angular the usual way with ./build.sh and then make the packages installable with for pkg in $(find dist/packages-dist -name package.json); do sed -i .bak 's/\$\$ANGULAR_VERSION\$\$/2.0.0-rc.2-snap/g' $pkg; done

To build rxjs into closure ES6, I have local edits in https://github.com/alexeagle/RxJS/tree/closure - then run npm run build_es6 to get the right files in dist/es6. Then npm run generate_packages to copy in a package.json.

We need a modification to tsickle to workaround https://github.com/ReactiveX/rxjs/issues/1703 - see https://github.com/angular/tsickle/tree/closure

Now I make an example app. Snapshot at https://github.com/alexeagle/closure-compiler-angular-bundling
In the vendor directory I've already installed the locally built closure compiler.jar, angular2 packages, rxjs, and tsickle, with something like
npm install ../angular/dist/packages-dist/{common,compiler_cli,compiler,core,platform-browser,platform-server}
npm install ../rxjs/dist/es6

So you can just npm install and npm run build to reproduce the closure bundle :)

So I would need to have a Java JRE installed to bundle a hello world Angular2 app?

Closure Compiler is just one option for the
tree-shake/bundle/Es6-to-5/minify pipeline. If you choose the closure
compiler option, you take a JRE dependency for now. But there is also a JS
version in the works:
https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/gwt/client/GwtRunner.java

On Fri, May 13, 2016 at 9:44 AM dpsthree [email protected] wrote:

So I would need to have a Java JRE installed to bundle a hello world
Angular2 app?


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-219096815

I wrote some notes on the hacks required to make it work. https://docs.google.com/document/d/17m1GwzXraKgbCkCmH1JnY9IZzPy4cqlpCFVhvlZnOys/edit

We reproduced @alexeagle's work and with a more up to date version of Angular. The entire build process is automatic, so you can follow everything from source to end result. You can see it in action at https://github.com/lucidsoftware/closure-typescript-example. Instructions for running the example can be found in the README for the example repo. There is a Docker container for the project, so it should be possible for pretty much anyone to try it out.

The above example utilizes Angular 2, clutz, and tsickle. Closure JS is used in Angular 2 TS, which is compiled to Closure compatible JS.

The changes we made to the dependencies to get this to work are largely the same as Alex's. There are a few changes we added and a few changes that are no longer necessary. Regardless, the basic idea is the same. We use a modified ngc or tsickle on Angular 2 and its dependencies to produce Closure compatible versions of those dependencies.

Closure Compiler

Building from head is no longer necessary. Just use one of the versions from June or July.

Angular

https://github.com/lucidsoftware/angular/tree/closure-bundle

The bulk of the work in the Angular 2 source is modifying ngc to produce Closure compilable js by adding a few tsickle steps to tsc-wrapped. There are a few hoops that we jump through in order to use the modified source as input to the TypeScript compiler in one of the tsickle passes. Otherwise, this is pretty straight forward.

We also change certain Angular modules to be compiled to Closure compatible JS by putting an angularCompilerOptions block in the tsconfig.json like so

"angularCompilerOptions": {
  "googleClosureOutput": true
}

Then when you run build.sh those modules have the output you want.

A test utillity file that uses selenium-webdriver is modified because selenium-webdriver is missing and we didn't want to make it closure compatible.

RxJS

https://github.com/lucidsoftware/rxjs/tree/closure-hack

The build process of RxJS is modified to build with the Makefile from our project. About half the build process is in JS and the other half in Make. It's pretty janky.

The only other changes besides the build target are a few small things to make RxJS compile with TypeScript and Closure.

symbol-observable (RxJS dependency)

https://github.com/lucidsoftware/symbol-observable/tree/closure

RxJS now depends on a some code that used to be part of the RxJS source and is now its own module. We modify that module (which is JS) to be compatible with the Closure compiler. There are only two files, so I did this manually.

tsickle

https://github.com/lucidsoftware/tsickle/tree/ignore-type-comments

We modify Tsickle in two ways:

  1. Add a --ignoreTypesInComments option. This prevents tsickle from throwing an error when it encounters jsdoc in comments. This is needed to compile RxJS which has a bunch of jsdoc comments.
  2. Modify the pathToModuleName for the CLI to be the same as the pathToModuleName we use in ngc. That way modules are named the same when run through tsickle or ngc. This should be committed upstream because the current pathToModuleName sometimes produces invalid goog.module names.
  • run a tree-shaker to remove ES6 modules not reachable from the entry point, in our demo we showed rollup
  • run a bundler to reduce down the module imports into a declarations in a single file; we showed system.js
  • down-level to ES5 - we showed this with TypeScript
  • minify to shorten symbol names, we used uglify.

Whilst it perhaps cannot match the final output size of the Closure Compiler, webpack 2 (using typescript loader and standard optimize plugins) gets you all of the these steps.

I would argue that the benefit of devs already using (dare I say _requiring_) a tool like webpack in their stack, and therefore not having to add more (hacked) dependencies, is more important than shaving off extra bytes in the final payload.

Would love to hear your thoughts!

@jjudd thank you so much for posting your repo and these notes, this is really helpful.

Do you have a minified app size you can share? Perhaps using Brotli compression so we have an apples-to-apples comparison with the hello world closure-ng2 example (which was 26.2k)

@JamesHenry we agree that webpack 2 is the way forward for most developers. Closure compiler is an expert tool and I doubt we can wrap it up in a package that 'just works' for developers who aren't already familiar with debugging the obscure ways its ADVANCED_OPTIMIZATIONS can bust your app.

@alexeagle We're currently working on getting this to work with ADVANCED_OPTIMIZATIONS. When we get that working I'll go ahead and post some benchmarks.

Converted the tsickle issue 1 above into a bug report there.

closure compiler is now available on npm in pure javascript : https://www.npmjs.com/package/google-closure-compiler-js

Wanted to provide an update:

We have the example repo and the beta Lucidchart editor working with advanced optimizations. We ran a short test using our version with simple optimizations, and our version using advanced optimizations is going out today or tomorrow.

The version of Angular used in the example repo has been upgraded to RC5 (HEAD as of Tuesday Aug 16).

The example repo and its accompanying Docker container have been updated in case anyone wants to try it out. Here's a link to the example repo https://github.com/lucidsoftware/closure-typescript-example

Bundle sizes

The final bundle size for the example project using advanced compilation are listed below. Note: the example repo includes code going from js -> ts using clutz and then ts -> js using tsickle. It is a bit bigger than just a simple hello world app.

Uncompressed: 112K
Brotli quality 10: 29K
Gzip: 34K

Notes on Getting This to Work

Here are the notes on what it took to get advanced optimizations working.In case I forgot something, I'm going to provide the links to all our forks, which have all of our commits.

Example repo

Externs files for Zone, Reflect, and Jasmine were included in the build process, so those function calls were not renamed.

To work around a Closure Compiler bug where static methods get lost, we have a really hacky sed command that we run on the built Angular js files, replacing all lines which include a static keyword with the line preceded by /** @nocollapse */. Here's a link to the Closure bug: https://github.com/google/closure-compiler/issues/1776

Angular

The biggest change we made to Angular is making it use the Tsickle annotated output during compilation. We thought we did this last time, but we had a bug where the annotated output was being thrown away :D

There were a few places that Angular refers to functions as strings. These functions were referred to in other places using dot notation. Accordingly, some references were renamed and others were not. We change Angular to use dot notation everywhere for those functions, so that they are always renamed.

We also updated Angular/Tsickle to not annotate .d.ts files.

We made Angular play nicely with ES6, so it works in uncompiled mode. We change some places where .apply was used on a constructor to use the new keyword.

Tsickle

We fixed a bug in tsickle where the CLI did not always include all the source files during annotation. The list of filenames from the TypeScript Program should have been used, instead we were using a different list of filenames.

Links to all our forks of projects to make this work

Angular: https://github.com/lucidsoftware/angular/tree/closure-bundle
Tsickle: https://github.com/lucidsoftware/tsickle/tree/closure-bundle
RxJS: https://github.com/lucidsoftware/rxjs/tree/closure-hack
symbol-observable: https://github.com/lucidsoftware/symbol-observable/tree/closure
Clutz: https://github.com/lucidsoftware/clutz

So we have a couple examples of this working, plus we use JSCompiler internally at Google so we have many apps doing this (though using the Bazel build system and some not-yet-released Bazel rules).

@robwormald @mprobst should we try to package this up in a more easily reproducible way for external users? I don't think we can claim success and close this issue yet.

The JS closure compiler looks really cool. However, I have seen issues where the process runs out of memory when targeting medium sized AOT projects via Rollup. Have you encountered this in your testing?

The Java version does not seem to suffer from this problem though.

https://github.com/google/closure-compiler-js/issues/23

I've heard that too. We have had to call node with
--max-old-space-size=4096 to get some tools to work in the past.

On Tue, Sep 20, 2016 at 11:27 AM Torgeir Helgevold [email protected]
wrote:

The JS closure compiler looks really cool. However, I have seen issues
where the process runs out of memory with targeting medium sized AOT
projects. Have you encountered this in your testing?

The Java version does not seem to suffer from this problem though.

google/closure-compiler-js#23
https://github.com/google/closure-compiler-js/issues/23


You are receiving this because you were assigned.

Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-248389501,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC5I_FDlMdHvHO2YCvypXju9wR8onzuks5qsCWmgaJpZM4IaZIi
.

I tried max-old-space-size with 8000 and 14000 on a Mac with 16GB of ram, but I still ran out of memory in my sample project.
Java version works though.

oh, that seems bad :)
could you file a bug on https://github.com/google/closure-compiler/issues

On Tue, Sep 20, 2016 at 11:52 AM Torgeir Helgevold [email protected]
wrote:

I tried max-old-space-size with 8000 and 14000 on a Mac with 16GB of ram,
but I still ran out of memory in my sample project.
Java version works though.


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-248396986,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC5I25uOXJ-Pt8a9WzQQUO1zRx4j1YUks5qsCthgaJpZM4IaZIi
.

Yup I have an active issue there already
https://github.com/google/closure-compiler-js/issues/23

@thelgevold that may be related to Typescript bug...try building with Typescript nightly build... (unfortunately, ngc doesn't support TS 2.1, so you'd have to go back and forward between that and 2.0.2).

@qdouble Hm interesting.. Are you saying it might be related to the JavaScript produced by TS 2.0.2?
Because by the time my code enters into rollup/closure compiler land, the code is already in JS format.

@thelgevold ah, with there is a bug that can stop your code from building if you have a lot of form controls and stuff with 2.0.2...if your files are already JS before you encounter the bug, then it may be something else...but I suppose you could try to use Typescript nightly to turn it into JS and see if it makes any difference.

Hi Guys, all of this sounds very interesting and we are looking into this to improve the performance of our website. What is the current status of the custom angular builds you are making. Will you be making a build for Angular v 2.0.0 ?

I will try to have some update on this for ngEurope...

On Fri, Oct 7, 2016, 1:48 AM Gerard A Lamusse [email protected]
wrote:

Hi Guys, all of this sounds very interesting and we are looking into this
to improve the performance of our website. What is the current status of
the custom angular builds you are making. Will you be making a build for
Angular v 2.0.0 ?


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-252186017,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC5I1TZZEGiuBu8zJe2BtYEzilr-VPHks5qxgdTgaJpZM4IaZIi
.

Hi @u12206050, I took a crack getting this upgraded to Angular 2.0.1 a couple weekends ago. I got it about 2/3 of the way there. The example works with simple optimizations, but not with advanced optimizations.

I'm no longer actively working on this, but another team here at Lucid I spoke with today wants to take a crack at it soon. If I get another free weekend, I'd like to finish moving it to 2.0.1. So whoever gets to it first.

In the meantime, if you would like a bit more info on this, you can check out the blog post we wrote on it at https://www.lucidchart.com/techblog/2016/09/26/improving-angular-2-load-times/. I hope that helps.

If you run into any issues and want to bounce some questions off of me, please feel free. It is likely going to be a bit of an adventure hooking it up to your build system, though. Best of luck! :)

any update from ngeurope by any chance?

after realizing that webpack2 tree shaking does _not_ tree shake reexports, and angular requires importing from reexports, this is quite a bummer

my obvious thought was oh, tsickle! thought I'd pipe ngc -> tsickle -> webpack -> gcc advanced (instead of just ngc -> webpack -> gcc simple) only to realize tsickle converts import/require to goog.require, so there is no easy webpack (or any other non-google bundler, really) integration here currently

angular core + forms + router + platform-browser + common is taking up ~30% of our main bundle with ngc + webpack2. that's ~100kb minified and gzipped, ouch

I'd love to see tsickle working with webpack, as clearly even the angular cli team think webpack is a great choice. I'm open to any suggestions on this one. Otherwise I'll take a deeper look at @jjudd's work, but I'd hate to waste the time if an official solution is just on the horizon (perhaps with @ngtools/webpack?)

https://docs.google.com/presentation/d/1SaHtM1_mpBZuN74wxAJSPQRB0sPbWRSJPQZsxx4_BpE/preview

Their Twitter feed seems to have some content.

Get Outlook for Androidhttps://aka.ms/ghei36

On Fri, Oct 28, 2016 at 12:52 AM +0200, "Steve Sewell" <[email protected]notifications@github.com> wrote:

any update from ngeurope by any chance?

after realizing that webpack tree shaking does not tree shake reexports, and angular requires importing from the reexports, this is quite a bummer

my obvious thought was oh, tsickle! thought I'd pipe ngc -> tsickle -> webpack (instead of just ngc -> webpack) only to realize tsickle converts import/require to goog.require, so there is no easy webpack integration here currently

angular core + forms + router + platform-browser + common is taking up ~30% of our main bundle with webpack2. that's ~100kb minified and gzipped, ouch

open to any suggestions on this one. otherwise I'll take a deeper look at @jjuddhttps://github.com/jjudd's work, but I'd hate to waste the time if an official solution is on the horizon

You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHubhttps://github.com/angular/angular/issues/8550#issuecomment-256791543, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAABN_sAdZ1RDtPIk1Zt4QJr7oUdg723ks5q4SqbgaJpZM4IaZIi.

Update: we had some serious discussion at ngEurope, with @robwormald and @hansl and others.
@pkozlowski-opensource is skeptical that the external community would embrace a new toolchain, rather than improving the existing one.
So I think the first step is that angular-cli will work to improve webpack to do a better job.

At the same time, I would still like to have a canonical seed project that demonstrates using closure compiler. @steve8708 I'm not sure what you mean about goog.require breaking a bundler, since closure compiler _is_ the bundler. No goog.require statements appear in its output. @jjudd is anyone at Lucidchart interested to collaborate to unroll our hacks and make it possible for the external community to repro the 26k ng2 app?

@alexeagle currently we use webpack for a lot of things - bundling, code splitting, preprocessing, linting, hashing, gzipping, and more, similar to what the angular CLI and a lot of the popular open source angular + webpack starters do

I'd really love to keep webpack for everything it does (esp. bc we can leverage a lot of community plugins to make some complex tasks simple and configurable) rather than reimplement everything we currently do (say with gcc + gulp).

I'd love to just be able to just be able to swap tsickle for tsc to add the jsdoc annotations during transpilation needed for advanced mode minification (we currently use a webpack gcc plugin in simple mode for minification)

Really appreciate your update though, and very eager to track the progress the angular-cli team makes with their webpack toolchain!

@steve8708 which tools do you want to be able to operate on the closure-annotated goog-module'd ES6 code before you hand it to your gcc plugin?

@alexeagle Thanks for the update. I'll talk with people here and see what interest there is.

As for an example, is the example repo we built for this what you were thinking of? https://github.com/lucidsoftware/closure-typescript-example

This sounds like a great thing for the CLI. It wraps Webpack and bunch of other plugins existing and new. It would be awesome if the CLI could use Closure compiler under the hood instead of uglifier. It's abstracted away from the user either way.

@jjudd yeah that's the best we have right now, but it's pinned to a particular (old) version of ng2 and its dependencies, right?
We also need to explain how to use externs so you can have non-closure-compatible dependencies.

True. We're actively working on moving to the latest version of Angular right now. It should hopefully be done very soon. I'll post an update when we get that done.

@alexeagle well frankly webpack is built around commonjs/amd/es6 modules, so a lot of features don't work properly (if at all) if modules aren't in this format. some quick ones that come to mind

  • url loader. We import assets in typescript (or html) as require('./asset.png') and simply by doing that webpack handles the copying, hashing, compressing, and loading of the images where needed
  • automatic code splitting (e.g. using an angular2 router loader like this one) and any other System.import throughout your code for easy lazy loading of any content
  • various other loaders - for example we use the import loader for angular2 material as material currently has window references to find typescript helpers like __extends so we must rewrite that to global (example). Even things like this really only work most elegantly (or, less hackily) with webpack when you have it handling your bundling as it is intended to
  • various other plugins - for example the html plugin will produce our base index.html after running the full build/bundle since from doing that it is aware of what entry javascript files have been created (including their full paths and hashes) and produces our root html template with the correct script urls with hashes, etc appropriately

frankly I've implemented this same (or similar) stuff plenty of times with anything from make/jakefiles, grunt, gulp, etc but I've never had nearly such a seamless and solid build tooling experience as using webpack (esp. for the amount of code you must write and maintain for your build), so I'd love to see tsickle allowing for more common (in open source land) module formats that can integrate with tools like webpack

tl;dr, webpack just depends on using commonjs, amd, or es imports, and they have quite an awesome system of loaders and plugins that can do a lot of great things as long as webpack can understand your modules in any of the common javascript formats, which unfortunately does not include any goog.* modules

lmk if there is anything else I can help to answer here or even code contributions I can make to try and use tsickle with other tools such as webpack. I'm very eager to get this going however possible!

and thanks!

@steve8708 the reason we currently hand goog.module syntax to closure compiler is that it has bugs/missing features for ES6 modules. Last time I checked they had no support for export * for example. The team has indicated they are not interested in supporting ES6 modules since they don't work natively in most browsers.

If you need to take TS code through these steps before bundling/treeshaking/minifying with closure compiler, we'd need to fix all the ES6 module support in closure compiler. I don't think anyone owns this right now.

Good to know, thanks @alexeagle

I just found some docs suggesting gcc supports commonjs modules as well. I'll test it out a bit and find out if maybe it's more implemented or less buggy than es6 modules currently.

If so I'll poke around and see if maybe it's possible to make the goog module conversion optional in tsickle and if there is a way to get the benefits of gcc advanced minification with jsdoc type annotations pulled from typescript but with commonjs or es6 modules. I think that would certainly be nice for myself and the rest of the community!

Thanks again for the info, it's very helpful!

Discussed some more here in-person ... if the only bug with closure compiler is export *, we could easily make a tsickle option to flatten that into export {symbol1, symbol2, ...} in the post-processing step. We could also add the JSDoc annotations (and include these in our ES6 distributions).

@alexeagle that'd be amazing to get the jsdoc annotations in the ES6 distributions!

From some testing today I found that tsickle actually already does convert export * to export { a, b, c } format for es6 modules, so that is great news!

Even better, I cloned tsickle locally and commented out the conversion to goog modules and was able to get tsickle + gcc advanced working perfectly with our webpack build just as hoped!

This got our main bundle down a full 15% smaller, and that's without anything in @angular/* being annotated yet. This is looking very exciting and promising

On top of that, from the investigation I did today I see that when you transpile with es6 modules none are replace with goog.require, only commonjs require calls are. This is really good news as it means projects can use tsickle as-is already today with tools that support esmodules like rollup and webpack2 as long as they replace _all_ commonjs require calls with es6 imports.

Fortunately ts 2.0 made this much easier with declare module '*', eliminating the need to use require for non-typescript code

I'm really eager to see how much more we can save when @angular/* es distributions have type annotations from tsickle as well! Especially while webpack2 still doesn't tree shake reexports the savings from removing lots of bundled but unused angular core/common/etc code here could be really big!

Decided to give this a try as well.

Cloned latest tsickle, but still running into a few issues:

For me there are two categories of errors:
1) export * compilation errors from closure - Same error described by others above...

node_modules/@angular/common/src/location.js:9 (JSC_CANNOT_CONVERT_YET)
ES6 transpilation of ''Wildcard export'' is not yet implemented.
export * from './location/location_strategy';

Will the solution be to publish Angular source without export * here ?

2) closure keywords in the angular source code comments causing compilation errors

node_modules/@angular/core/src/metadata/ng_module.js:15 (JSC_PARSE_ERROR)
Parse error. illegal use of unknown JSDoc tag "stable"; ignoring it
 * @stable

I am doing my bundling using rollup with the closure compiler plugin. I am using rxjs-es instead of rxjs since I ran into a few issues with commonJS conversions.

I am curious about the plan for tsickle vs ngc. From what I can tell you now have to run them in tandem ngc -> tsickle. Are there plans to combine them to reduce this to one step?

I am very interested in seeing how far this can go. I guess it really comes down to the structure of the code, but there could be potential for huge code reductions. Did a non angular POC with closure here: http://www.syntaxsuccess.com/viewarticle/tree-shaking-in-javascript

From what I can tell, Closure has an enormous potential when it comes to code optimization. My sample reduced several classes to a one liner, but still unclear how well this will translate to Angular applications. Hopeful though!

Now that PR is in so you can use

    "@angular/common": "angular/common-builds",
    "@angular/compiler": "angular/compiler-builds",
    "@angular/compiler-cli": "angular/compiler-cli-builds",
    "@angular/core": "angular/core-builds",
    "@angular/platform-browser": "angular/platform-browser-builds",
    "@angular/platform-server": "angular/platform-server-builds",
    "@angular/tsc-wrapped": "angular/tsc-wrapped-builds",

and there is no export * and all the code has JSDoc for closure.

@thelgevold @steve8708 want to try again?
I'll investigate https://github.com/cramforce/splittable with @robwormald

@alexeagle awesome!

I'm trying this out now and when using the -builds versions I get this error from ngc:

Error: Metadata version mismatch for module [path to a project .ts file], found version 1, expected 2

I'm not fully sure the meaning of this message, do you have any thoughts on how to resolve it?

@tbosch made a change subsequent to mine. He updated the metadata collector, and now Angular expects to read version 2 metadata. However, when version 1 metadata is encountered it should upgrade-in-place. Looks like a bug, I'll let Tobias confirm...

one thing to try, do you have any generated .metadata.json files in your project? try cleaning them and retry?

@alexeagle I have been playing with this a little bit, but I am running into compilation issues with named imports like @angular/core

I think I have read somewhere that closure doesn't currently handle named imports. If I go through and convert the imports to absolute paths it seems to fix the compilation errors. There are too many errors to fix them all manually though.

Converting references like @angular/core to ../../core/index in angular/application code seems to work....

Here is a sample error:

node_modules/@angular/common/src/pipes/slice_pipe.js:8: ERROR - required "module$$angular$core" namespace never provided
import { Pipe } from '@angular/core';

Gave your repo a try as well and it seems like the same thing is happening there too.

I wonder if something could be added to closure to resolve a named import like @angular/core to the corresponding physical path.

@thelgevold I found that as well. The problem is that node_modules/@angular/core/index.js is registered as module$$angular$core$index. Since ESModule semantics are undefined, there is no spec saying that "foo/index.js" may be imported with "foo". Since CommonJS allows this, rollup and systemjs support it, but it's unspecified. Therefore the response from the closure team has been that it's a module loader problem, and they don't support this. https://github.com/google/closure-compiler/issues/1710 (and also a chat with @MatrixFrog)
I suspect that changing import {} from '@angular/core' to from '@angular/core/index' solves this as well, though it's not a useful workaround since user code would have to do this too.

I imagine that converting the module syntax to commonjs is another approach, though adding a tool to do this makes the build that much harder for anyone to reliably reproduce.

Yeah, unfortunately that's the same issue I'm hung up on now and not sure if there really is a straightforward solution :(

We may have to give up on tsickle again since even if for our own imports we add /index to the end of paths, imports within libraries (e.g. @angular/platform-browser importing from @angular/core, etc) will also not have the correct paths for gcc support

I'll give it more thought but in the meantime it sounds like gcc advanced may not be a possibility for the general user anytime soon unless anyone else has any other ideas

Would it be possible to have ngc output JS with the long hand notation for imports to shield the user from using them in their own TypeScript code.

Then as part of the A2 release do the same for the angular code? Basically publish a version with long form imports?

That's a good idea - internally we have tsickle convert to goog.module
syntax, so we should be able to use a different conversion to give explicit
imports from /index.js

On Wed, Nov 30, 2016 at 11:35 AM Torgeir Helgevold notifications@github.com
wrote:

Would it be possible to have ngc output JS with the long hand notation for
imports to shield the user from using them in their own TypeScript code.

Then as part of the A2 release do the same for the angular code? Basically
publish a version with long form imports?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-263971981,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC5Iy255poYsJTn8ZdBAgVZWbagFwzAks5rDdARgaJpZM4IaZIi
.

Ah that's an awesome idea!

I wonder though, would/could that also cover non angular libraries? I.e. does tsickle parse non typescript/angular modules so that it could also convert import { foo } from 'other-lib' to import { foo } from 'other-lib/index' where needed?

Likewise, one other element of module resolution some of the major loaders use in mimicking of node module resolution if other-lib has a main field in their package.json, say pointing to main.js, would it also (in theory, if implemented) be able to convert imports from that package to import { foo } from 'other-lib/main'?

This mostly just depends on how deep tsickle traverses your code when run from the command line. Seems to me if it's not traversing everything this module resolution issue would still be a problem for any other 3rd party lib people might be using

If this is all possible, that would be quite amazing and mean the ability to use tsickle could be put back on our radar which would be amazing!

In general, only carefully maintained libraries can be optimized by closure, it breaks others. So the typical usage will be

  • Closure compile your code, Angular and its deps (esp. rxjs) into one or more bundles
  • give closure externs files for other libraries you need to interact to/from the closure bundle
  • Load other library bundles using their minified js distribution in a separate script tag

This is what we do within Google too. So there should be no need to run tsickle, just ngc.

Ah I see, thank you for noting @alexeagle!

Just in case we get lucky, I asked TS to do this:
https://github.com/Microsoft/TypeScript/issues/12597
In the meantime, maybe we can do it in tsickle, I'm trying it now. Here's the issue for it:
https://github.com/angular/tsickle/issues/288

Update: that tsickle issue is resolved. Building angular from this branch:
https://github.com/alexeagle/angular/tree/closure
I now produce an ES6 distro with all the explicit import {} from '@angular/core/index'

Next issue is that we need a compatible distro of rxjs. @jayphelps has been looking at the rxjs packaging story, which has been put off until after 5.0.0-final. We'll probably still need a custom experimental distro of rxjs in the meantime as well.

This is really great work @alexeagle

I forked your project and add a few more "advanced" samples. The great news is that the closure compiler part is really transparent and not in our way at all.

I added Dynamic Forms (Reactive Forms) and Recursive Treeview samples and everything works well.

Only tweaks I had to make were a few additions to the closure compiler config to get access to Forms directives and ngIF, ngFor etc

Fork:
https://github.com/thelgevold/closure-compiler-angular-bundling/tree/add-samples

I deployed out a version of the sample here too:
http://www.syntaxsuccess.com/a2-closure/

Awesome, thanks for picking this up so quickly @thelgevold
For others watching the thread: I updated the example repo today, we have a working closure compiler setup again, and now there's no separate polyfills script. Zone.js is built into the closure bundle.

I think the next steps are to try this in some real world scenarios. Ionic is doing this too. Then sometime next month we'll try to get the Closure Compiler team in a room with us and burn down the biggest usability issues. In the meantime @mhevery is helping to point out code that is retained but should have been shaken out.

Question:
I took a look at including third party libs when doing closure compilation.

The challenge there to normalizing the import paths using the TypeScript compiler is that third party libs are most likely published with just JS source.
This makes it more cumbersome to normalize barrel imports without index at the end of the path.

Have there been any discussions about reversing the recommendation to favor node moduleResolution?

I am thinking it wouldn't be so bad to instead use classic moduleResolution and alway include the index in import paths.

I am guessing there might a performance penalty during compilation in large projects. Especially if the compiler has to do look-ups for every import to detect if it has to rewrite it to be compatible with closure compiler imports.

That said, closure compiler compilation (ADVANCED_OPTIMIZATION) in isolation is typically a time consuming step. Maybe the added time isn't all that noticeable?

Thoughts?

I agree, our compile time is impacted by the index lookup I added to
tsickle - for every import we have to do a moduleResolution again.
Internally we convert commonJS modules to goog.module/goog.require - I
imagine it would be too slow for us to do the extra tsickle step in dev
mode.

We can discuss the question of supporting node_module-style resolution next
week, we have a hackathon day with the Closure Compiler team.

That said, even if closure compiler understood the module syntax for
third-party libraries, typical code gets broken by ADVANCED_OPTIMIZATIONS,
largely due to property access syntax (dot syntax is renamed, square
bracket syntax is not). So I think third party libraries will have to stay
outside the closure compilation. Internally, we just take their -min.js
distro and concat it onto the end of the closure bundled JS.

We have imagined it would be great if some file or package could opt-out
from closure optimizations. One practical way I could imagine doing this is
to add a "closureCompilerCompatible: true" to the package.json of libraries
that have verified their closure advanced optimization compat. Then a build
tool could select the libraries that are compiled into the bundle, vs.
those which are resolved using closure externs.

On Thu, Jan 12, 2017 at 6:59 PM Torgeir Helgevold notifications@github.com
wrote:

Question:
I took a look at including third party libs when doing closure compilation.

The challenge there to normalizing the import paths using the TypeScript
compiler is that third party libs are most likely published with just JS
source.
This makes it more cumbersome to normalize barrel imports without index
at the end of the path.

Have there been any discussions about reversing the recommendation to
favor node moduleResolution.

I am thinking it wouldn't be so bad to instead use classic
moduleResolution and alway include the index in import paths.

I am guessing there might a performance penalty during compilation in
large projects. Especially if the compiler has to do look-ups for every
import to detect if it has to rewrite it to be compatible with closure
compiler imports.

That said, closure compiler compilation (ADVANCED_OPTIMIZATION) in
isolation is typically a time consuming step. Maybe the added time isn't
all that noticeable?

Thoughts?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/8550#issuecomment-272348788,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC5I49thgsBW1VGc3oyFVvEchxI2D6Zks5rRuivgaJpZM4IaZIi
.

Thanks for the reply. I enjoy following the progress on this project.

Yeah I definitely see some challenges when integrating with third party libs - especially ones that were written without the closure compiler in mind. I like the proposal to include some sort of closure compiler compatibility flag though.

My experience with the closure compiler is that it potentially offers incredible optimizations, but forces some conventions on you. I still see it as an expert tool, but I wonder if there are ways in ngc to simplify things by generating code that complies with some of these conventions. Externs will be case by case based on specific libraries constants, but another common use case is http calls.

When dealing with http calls....
The way I see it we will have to "square bracket" protect some of our model properties to prevent the Closure compiler from shortening property names in references to the payload object. Otherwise the shortened versions will no longer match the runtime values from the actual http payload.

I have generally protected properties as needed by using the square bracket notation {'firstName': 'Joe'}.
This approach works well for me, but some people may not love that we have to write code like this though. Maybe a decorator or similar could be a cue to ngc to generate JS in this format in cases where property names mush be protected?

Within tsickle we've made it so
declare interface User { firstName: string; }
(with the "declare") informs the Closure compiler to not mangle use of the
'firstName' field.

This is vaguely consistent with how it already works in TS, where "declare"
is used to represent types of things that are outside of your TS code.

@evmar That's excellent! Thanks for telling me about this.

I have updated my fork to use the new snapshot build from the angular repo. https://github.com/thelgevold/closure-compiler-angular-bundling

As far as I know the only non-standard dependency at this point is a custom rxjs build.

To try out more realistic scenarios I've started to port over my demo components to the closure compiler fork.

Here are two builds if you are interested in a comparison between a standard rollup build and a closure compiler build of my components:

http://www.syntaxsuccess.com/closure-compiler-demo
http://www.syntaxsuccess.com/rollup-compare-demo

I am not known for css skills, so the demo seriously lacks styling, but it is interesting to compare the two builds wrt size:

Here are the size stats:
Rollup: 130k
Closure: 79.2k

In the Closure build you can also skip the core-js shim if your browser supports es6 (Chrome, Edge etc). The Rollup build requires the shim regardless of es6 support in the browser. This is a side effect of not shaking out the decorators.

I will expand on this demo over time. It currently uses The Router, Forms and ReactiveForms.

So far it's been relatively straightforward to code against the closure compiler. The only time I had to make special tweaks to make it work was in the "sortable grid" demo. The grid does dynamic lookups by column name that are not compatible with mangling of property names.

As discussed earlier it may be possible to prevent mangling of properties by using an interface with a declare
ex:

export declare interface Person {
  firstName: string;
  lastName: string;
  age: number;
}

This might be a problem on my side, but the interface with declare didn't seem leave cues for the Closure compiler that would prevent mangling. As a workaround I have been "square bracket" protecting the property names. This works well, but it would be nice to solve it with the interface instead.

Here are the size stats:
Rollup: 130k
Closure: 79.2k

I'm surprised. Having used the closure-compiler in advanced mode for years, I would have thought the difference would be larger... But I guess Angular is a large framework.

In my case the split between Angular vs Application code / rxjs is roughly 70/30 .

Here is the source map explorer view into the bundle: http://www.syntaxsuccess.com/closure-compiler-demo-map

@b-strauss Misko believes that about half the weight of Angular should tree-shake out, but Closure doesn't understand some of our coding patterns are side-effect free so we miss on several optimizations. Expect that closure number to go down maybe 20k or so over time relative to the alternatives.

@thelgevold technically Angular should only depend on ES6 promises and collections (possibly iterable but I think that's optional), not all of es6-shim. https://github.com/angular/angular/blob/master/modules/tsconfig.json#L22

@thelgevold yes, we should hook up the closure externs, it should not be hard (after we get some tsickle changes related to TS 2.1 upgrade rebased and merged)
https://github.com/angular/angular/issues/14325

@alexeagle Would it be more optimized to somehow transpile to the square bracket equivalent for these cases?

Not sure if it will work, but my understanding is that externs should be limited to external framework references like global variables "owned" by other frameworks.

I think Closure can do a better job at optimizing quoted props through a combination of a single full length reference, and shortened internal references to the same property.

I believe externs are less optimized since it won't touch any references to the property name. Meaning 50 refs to firstName will remain as firstName, while square brackets might leave a single public ref as firstName and mangle the other 49 internal refs.

@thelgevold that's true, the externs approach marks that property name as un-renamable everywhere.
We don't have any way to automatically re-write dot-access to quoted-access, though I suppose with the type system we could see the declare interface and know when to do it.

But after compression, that may not matter? If firstName goes into the dictionary at the header of the compressed file, it may not make much difference how many times it's referenced in the code.

FWIW this is the approach we take right now internally at Google. But I think we should experiment with your suggestion if we get time.

I'm pretty sure closure does not optimize quoted props in any way. Closure just removes the square bracket notation from this: model['firstname'] to this x.firstname.

AFAIK the "clean" way with extern files and the square notation way does produce the same result.

the problem is that notModel.firstName will also be y.firstname instead of y.z

Closure should be able to nominally distinguish the types of these instances by the types provided in the externs. That's how it worked in the old "global" world. I have no idea how to generate externs for classes that have the same name but come from different modules.

@alexeagle You are probably right. There might not be much of a gain after gzip.

It would probably be complicated as well since you would most likely have to discover all the "same" firstName references and rewrite them to ['firstName'] all the way through. Otherwise there would be a mix of .firstName notation and ['firstName'], which would break at runtime.

@b-strauss Closure will convert ['firstName'] to .firstName. However, here is the optimization theory behind [''] vs externs: https://developers.google.com/closure/compiler/docs/api-tutorial3
I have seen this behavior in some of my experiments, but it may not be worth the effort here.

For mobile larger JS source file will still take longer to parse. It's not just a question of post gzip size.

@thelgevold I know the theory behind it. :)

I just wrote a little test:

This app code:

/**
 * @constructor
 */
function NotMyModel() {
  this.firstname = 'foo';
}

console.log(new NotMyModel().firstname);
console.log(new MyModel().firstname);

together with this third-party code:

<script>
  function MyModel() {
    this.firstname = 'foo';
  }
</script>

and these externs:

/**
 * @constructor
 */
function MyModel() {
}

/**
 * @type {string|null}
 */
MyModel.prototype.firstname;

produces the following output:

(function () {
  console.log("foo");
  console.log((new MyModel).firstname);
})();

Closure is able to distinguish the two firstnames based on the nominal types, and even inlines the first one. As I said, in the old "global" world this was not a problem, because best practice was to always prefix your types com.bstrauss.MyClass.

The question is how should tsickle generate externs in a world where types from two different modules could have the same name?

AFAIK there is no equivalent goog.module for externs.

@b-strauss Thanks, for sharing that experiment.

My original comment was more about how [''] might optimize multiple references to the same MyModel.firstName property. Not so much about optimizing for crossover wrt same names across different model objects.

My understanding is that if you have 10 MyModel().firstname refs in your code, using [''] could potentially allow Closure to create an internal short version alias. That way the net size is smaller since it preserves firstName where other code expects it, but benefits from the shorter name for private/local refs. It's my understanding that an extern will not give you that benefit since it leaves all references alone.

@b-strauss my plan was to tackle this in https://github.com/angular/tsickle/issues/352, by mangling the externs name and then aliasing locally. That is, for a file foo/bar/baz.ts:

/** @externs */
function tsickle_from_foo_bar_baz_ts_MyModel() {}
/** @type {string} */
tsickle_from_foo_bar_baz_ts_MyModel.prototype.firstName;

And at the usage site:

goog.module('foo.bar.baz');

// Alias:
var MyModel = tsickle_from_foo_bar_baz_ts_MyModel;

// ... later on ...
new MyModel().firstName;

Let's take the discussion to that issue, this thread is becoming a bit unfocused.

A while back in this thread there is a mention of discussion about how well accepted a Closure Compiler based build chain might be by the community. Even if it remains secondary numerically to the Webpack stack in Angular-CLI, I suspect it will still get decent adoption if it provides a smaller output. Size still matters a lot, particularly on mobile.

@alexeagle re "third party libraries will have to stay outside the closure compilation" - That's probably a good starting point, but if things get cleaned up where Closure can be used almost trivially on Angular application code, I believe this will create considerable pressure on Angular add-on library makers (though perhaps not the broader ecosystem) to verify their stuff works with Closure. The same thing has been happening over the last few months regarding AOT, in the early fall most add-on libraries said "AOT?" but today most of them that I stumble across have things working with AOT or are well aware and heading that direction.

If anyone been following this work in the various repos linked in this issue over the last year, I think the latest ones I've seen above are still pointing at the "-builds" packages, which were necessary until recently. But now Angular 4 rc ships with ES2015 modules, so that is no longer necessary. Here's a branch I've updated to work with the latest, in the process of catching up on understanding what's been going on with Closure use.

https://github.com/kylecordes/closure-compiler-angular-bundling/tree/update-versions

It feels like we are on the verge of a production-grade Angular build chain consisting of just ngc (with tsc inside) plus Closure. That is a nice, short stack.

I published the follow repository showing how to use 4rc.2, ngc (AOT), Closure Compiler:

https://github.com/OasisDigital/angular-aot-closure

Technologically this has little to offer over Alex's, but it has a different goal. I'm trying to make this repository contain plenty of comments and text explaining how and why things are. Therefore please feel free to open an issue on it, if there is anything not sufficiently explained in the comments or README.

@nosachamos Wild guess: something in the toolchain (probably tsickle or Closure?) did not anticipate a parameter named this. My suggestion is:

  • Edit your local copy of RxJS source, change that parameter name, confirm fixed. If so then...
  • Put in an issue (or even a PR) to RxJS to rename this this and any similar this parameters. Hopefully the RxJS team will see the merit in such a change;possibly a good word from the Angular team ( @alexeagle ?) on such an issue will help.
  • Put in an issue with a simple repro case to Closure, about the parameter named this... and it doesn't repro there, look elsewhere in the tool chain (tsickle?).

@kylecordes Ops, I found my issue and deleted the comment I guess as you were typing.

For the record, my issue was that an argument named "this" was being removed during transpilation.

It turns out, closure was invoking that function using call, so this was actually set that way and needed not be passed in. If I rename "this" to "this2", the argument is kept. I guess the transpiler is smarter than I thought.

My issue was within ngrx effects, not rxjs. Ngrx/effects accesses the effects through their names, using the brackets notation, which is broken by the closure compiler when it renames all the things. To prevent the issue, in the constructor of your classes containing your effects, assign their names to "this" using brackets notation:

export class MyEffects {

  @Effect()
  doSomething$: Observable<Action> = this.actions$ ...

  constructor() {
    this['doSomething$'] = this.doSomething$;
  }

}

So I'm almost up and running on a real world app using the ngrx stack. I'm writing what I believe to be the last manual extern required for my case. If this all runs well, I'll create a branch on the sample app here with the ngrx stack as a demo. I'm using my own fork of ngrx/core and ngrx/store while my PRs for expanding wildcard exports are not merged, but other than that, I think this will go well.

I'll keep you guys posted.

Can you file a bug against tsickle with more information on the this issue? Our intent is that you shouldn't need to manually rename variables like this.

Is it possible to fix ngrx to not use brackets?

@nosachamos based on your deleted comment, and my maybe-should-be deleted comment, is there any issue to report? (per @evmar )

@kylecordes Maybe,
but what I think it's happening is that the transpiler can figure out the function has "this" set by the call invocation. So it can in this case safely remove the argument, as this will be whatever is passed to call. That would not be true if the argument gets renamed, so it keeps the argument. I think in "this" particular case, the transpiler is fine. In fact, renaming the argument doesn't fix the problem, as the real issue was the bracket access.

@evmar I believe so. This bracket access is quite central to their operation but I suspect annotating the underlying dictionary with closure's @dict annotation would suffice to fix the issue. I will give this a try and submit a PR to them if that works. Else, I'll submit a PR to their README with a note on the bracket workaround when using ngrx/effects with closure.

Note that any Closure type annotation added to TS code is stripped by Tsickle, so adding an @dict there won't help.

My issue was within ngrx effects, not rxjs. Ngrx/effects accesses the effects through their names, using the brackets notation, which is broken by the closure compiler when it renames all the things. To prevent the issue, in the constructor of your classes containing your effects, assign their names to "this" using brackets notation:

@nosachamos Won't the same issue happen with @Input() and @Output()? Another question would be: should closure rename decorated fields?

@evmar I'm transpiling with removeComments: false and all my closure annotations are passed onto the ES6 code I feed closure, so that should be fine. I'm using @dict in other places, and it's fine.

@awerlang no, the @dict is a jsdoc style annotation, not an actual annotation like @Input. These closure annotations go on comments.

@nosachamos our test case is this input https://github.com/angular/tsickle/blob/master/test_files/jsdoc/jsdoc.ts producing this output
https://github.com/angular/tsickle/blob/master/test_files/jsdoc/jsdoc.js
where you'll see we munge most annotations. Maybe there's a bug though.

@evmar oh, I'm compiling with ngc and using

    "target": "es6",
    "module": "es2015",

and

  "angularCompilerOptions": {
    "skipMetadataEmit": true,
    "annotationsAs": "static fields",
    "annotateForClosureCompiler": true
  },

So I get just ES6, no goog.modules.. so everything seems to be working as intended, and I get the closure annotations. This is for RxJs. For packages that interact with angular (components, modules, etc), then skipMetadataEmit is set to false.

@nosachamos as I understand, the tsickle step can't do it's thing, and won't do its thing when run via ngc, unless you are using "module": "common". Tsickle enforces this limitation when run by its own CLI, does it gain the ability to work with es2015 module output when run via ngc? Do you see tsickle-generated JSDoc in the es2015 module output?

@kylecordes yeah, I get the same exact output you get in the demo repo you posted above compiling with tsc-wrapped and the rxjs-tsickle tsconfig.

could we move bugs to another issue? it doesn't scale to address individual problems on the tracking issue. Thanks!

@alexeagle absolutely. I posted here initially because I thought this was relevant (a problem in how rxjs was declaring arguments named "this" that were being lost). There's nothing to follow up, but if something comes up we'll do in a separate issue.

@nosachamos Indeed... I just found that indeed, tsickle-via-ngc or -cli-wrapped can handle module:es2015.

@alexeagle Ah, oops. At 90+ comments, I didn't realize it was a tracking issue. Will make new issues if they come up.

Took another look at the generated bundle. Noticed that there is definitely room for further code reductions here.

It's not always easy for Closure to identify unnecessary code since the existing code structure sometimes makes it seem like the code is needed.

Based on the POC app in @alexeagle 's repo I picked a few unnecessarily included elements and made a few temporary tweaks to the source to force them out of the bundle.

Seems like there are a few repeating patterns here:

A service class may not be needed by the application, but if the service is added to a provider array along the way, it may cause the class to make it into the bundle. Examples of this are AnimationDriver and AnimationQueue. By removing the provider references to these classes I am able to get these classes out of the bundle.

The sample app in Alex's repo does not do any QueryList querying, but a lot of the Query related code is still in the bundle.
In this case Closure can't exclude the code because the code lives in a switch statement where multiple scenarios are handled.
Take a look at createViewNodes in core.js and the NodeType.Query clause in particular.

case NodeType.Query:
       nodeData = (createQuery());
       break;

The conditions in this method are resolved at runtime , so Closure has to include all the code to satisfy all the conditions in the switch. I removed this particular clause and the code was excluded from the bundle.

These are just two simple examples, but if you continue down this path, it will eventfully add up to noticeable reductions in size. I didn't go very far with this sample, but I probably removed roughly 1k by addressing 4-5 cases like this.

I also did a second experiment where I removed a bunch of code manually (without using Closure). I got my app down to 16.6k and I believe there is still room for removing more. Here is the sample: http://www.syntaxsuccess.com/viewarticle/minimal-angular-application . This particular experiment is a major hack, but at least it proves that you can stand up an Angular app with very little code.

To help with some of these challenges I was thinking it would perhaps make sense to extend AoT to generate more of the framework code - not just the view code.

Just thinking out loud here, but couldn't the ngc compiler in theory generate code for stuff like createViewNodes based on the needs of specific applications. That way if your app doesn't need Pipes, QueryList or whatever, those particular case statements would be omitted from the generated code.

Thoughts?

Based on what I learned from manually removing code in the bundles, I have applied similar modifications to the bundles in the closure fork.

I put the tweaks in a branch in case you are interested in seeing the results: https://github.com/thelgevold/closure-compiler-angular-bundling-old/tree/tweaking-src

Here is a diff of the original bundles vs the tweaked bundles: https://github.com/thelgevold/closure-compiler-angular-bundling-old/commit/d1b3954e7e8a5b2dbb193683f9a3057322870d60

If you want to try it, just replace the default bundles with the modified ones. I am assuming Angular 4.0.0 for this experiment.

Here are the numbers with the new bundles:

The new size is now ~15k gzipped, and slightly smaller with brotli.

++ ls -alH dist/bundle.js dist/bundle.js.brotli dist/bundle.js.gz dist/bundle.js.map
-rw-r--r--  1 tor  staff   47487 Mar 26 13:58 dist/bundle.js
-rw-r--r--  1 tor  staff   13979 Mar 26 13:58 dist/bundle.js.brotli
-rw-r--r--  1 tor  staff   15482 Mar 26 13:58 dist/bundle.js.gz
-rw-r--r--  1 tor  staff  133888 Mar 26 13:58 dist/bundle.js.map
++ for script in dist/bundle.js node_modules/zone.js/dist/zone.min.js
++ gzip --keep -f node_modules/zone.js/dist/zone.min.js
++ bro --force --quality 10 --input node_modules/zone.js/dist/zone.min.js --output node_modules/zone.js/dist/zone.min.js.brotli
++ ls -alH node_modules/zone.js/dist/zone.min.js node_modules/zone.js/dist/zone.min.js.brotli node_modules/zone.js/dist/zone.min.js.gz
-rw-r--r--  1 tor  staff  29634 Mar 25 11:00 node_modules/zone.js/dist/zone.min.js
-rw-------  1 tor  staff   8759 Mar 26 13:58 node_modules/zone.js/dist/zone.min.js.brotli
-rw-r--r--  1 tor  staff   9516 Mar 25 11:00 node_modules/zone.js/dist/zone.min.js.gz

Obviously it's hard to replicate this without restructuring the Angular source, but it's at least interesting to see how small a basic app can get.

Not sure how useful this is, but it's at least a fun experiment :-)

In summary:
A lot of the savings are from removing code from switch/if-else structures. Closure can't currently infer that some of the options are not possible at runtime for a given application. Another case is provider arrays populated with unused services.

As I understand from @thelgevold 's work, there is considerable output size reduction, with relatively minor-to-moderate adjustment to Angular:

  • Accommodate fewer features with if and switch in the source code.
  • Pre-configured access again to fewer of those features, in the provider arrays built in.
  • Adjust the compiler (affecting both JIT and AOT) to generate the additional bits of code to wire up those features/providers in the generated code... if the features are actually used.

I'm interested in what someone on the core team thinks of this, whether it really is minor-to-moderate and whether there is interest in going down this path.

Today we got a release of rxjs that works with closure compiler, and I've updated the example repo
https://github.com/alexeagle/closure-compiler-angular-bundling

The externs are cleaned up as well - Angular and Zone.js both distribute the needed externs in their respective packages.
We'll add some documentation and an announcement about the support shortly.

Now that all of this is out of the way, at this point do you have an idea of how it will end up integrating with the rest of the existing tooling?

e.g. might there eventually be an option added to @ngtools/webpack to automatically run the AOT output through Closure (and/or some documented configuration steps for webpack-closure-compiler)? Or is this something that will necessarily have to supersede webpack?

Is possible to implement ngx-bootstrap with Closure?

https://github.com/valor-software/ngx-bootstrap

Wanted to provide an update:

We have the example repo and the beta Lucidchart editor working with advanced optimizations. We ran a short test using our version with simple optimizations, and our version using advanced optimizations is going out today or tomorrow.

The version of Angular used in the example repo has been upgraded to RC5 (HEAD as of Tuesday Aug 16).

The example repo and its accompanying Docker container have been updated in case anyone wants to try it out. Here's a link to the example repo https://github.com/lucidsoftware/closure-typescript-example

Bundle sizes

The final bundle size for the example project using advanced compilation are listed below. Note: the example repo includes code going from js -> ts using clutz and then ts -> js using tsickle. It is a bit bigger than just a simple hello world app.

Uncompressed: 112K
Brotli quality 10: 29K
Gzip: 34K

Notes on Getting This to Work

Here are the notes on what it took to get advanced optimizations working.In case I forgot something, I'm going to provide the links to all our forks, which have all of our commits.

Example repo

Externs files for Zone, Reflect, and Jasmine were included in the build process, so those function calls were not renamed.

To work around a Closure Compiler bug where static methods get lost, we have a really hacky sed command that we run on the built Angular js files, replacing all lines which include a static keyword with the line preceded by /** @nocollapse */. Here's a link to the Closure bug: google/closure-compiler#1776

Angular

The biggest change we made to Angular is making it use the Tsickle annotated output during compilation. We thought we did this last time, but we had a bug where the annotated output was being thrown away :D

There were a few places that Angular refers to functions as strings. These functions were referred to in other places using dot notation. Accordingly, some references were renamed and others were not. We change Angular to use dot notation everywhere for those functions, so that they are always renamed.

We also updated Angular/Tsickle to not annotate .d.ts files.

We made Angular play nicely with ES6, so it works in uncompiled mode. We change some places where .apply was used on a constructor to use the new keyword.

Tsickle

We fixed a bug in tsickle where the CLI did not always include all the source files during annotation. The list of filenames from the TypeScript Program should have been used, instead we were using a different list of filenames.

Links to all our forks of projects to make this work

Angular: https://github.com/lucidsoftware/angular/tree/closure-bundle
Tsickle: https://github.com/lucidsoftware/tsickle/tree/closure-bundle
RxJS: https://github.com/lucidsoftware/rxjs/tree/closure-hack
symbol-observable: https://github.com/lucidsoftware/symbol-observable/tree/closure
Clutz: https://github.com/lucidsoftware/clutz

We reproduced @alexeagle's work and with a more up to date version of Angular. The entire build process is automatic, so you can follow everything from source to end result. You can see it in action at https://github.com/lucidsoftware/closure-typescript-example. Instructions for running the example can be found in the README for the example repo. There is a Docker container for the project, so it should be possible for pretty much anyone to try it out.

The above example utilizes Angular 2, clutz, and tsickle. Closure JS is used in Angular 2 TS, which is compiled to Closure compatible JS.

The changes we made to the dependencies to get this to work are largely the same as Alex's. There are a few changes we added and a few changes that are no longer necessary. Regardless, the basic idea is the same. We use a modified ngc or tsickle on Angular 2 and its dependencies to produce Closure compatible versions of those dependencies.

Closure Compiler

Building from head is no longer necessary. Just use one of the versions from June or July.

Angular

https://github.com/lucidsoftware/angular/tree/closure-bundle

The bulk of the work in the Angular 2 source is modifying ngc to produce Closure compilable js by adding a few tsickle steps to tsc-wrapped. There are a few hoops that we jump through in order to use the modified source as input to the TypeScript compiler in one of the tsickle passes. Otherwise, this is pretty straight forward.

We also change certain Angular modules to be compiled to Closure compatible JS by putting an angularCompilerOptions block in the tsconfig.json like so

"angularCompilerOptions": {
  "googleClosureOutput": true
}

Then when you run build.sh those modules have the output you want.

A test utillity file that uses selenium-webdriver is modified because selenium-webdriver is missing and we didn't want to make it closure compatible.

RxJS

https://github.com/lucidsoftware/rxjs/tree/closure-hack

The build process of RxJS is modified to build with the Makefile from our project. About half the build process is in JS and the other half in Make. It's pretty janky.

The only other changes besides the build target are a few small things to make RxJS compile with TypeScript and Closure.

symbol-observable (RxJS dependency)

https://github.com/lucidsoftware/symbol-observable/tree/closure

RxJS now depends on a some code that used to be part of the RxJS source and is now its own module. We modify that module (which is JS) to be compatible with the Closure compiler. There are only two files, so I did this manually.

tsickle

https://github.com/lucidsoftware/tsickle/tree/ignore-type-comments

We modify Tsickle in two ways:

  1. Add a --ignoreTypesInComments option. This prevents tsickle from throwing an error when it encounters jsdoc in comments. This is needed to compile RxJS which has a bunch of jsdoc comments.
  2. Modify the pathToModuleName for the CLI to be the same as the pathToModuleName we use in ngc. That way modules are named the same when run through tsickle or ngc. This should be committed upstream because the current pathToModuleName sometimes produces invalid goog.module names.

hi I installed your project but when do "make run" get Error :
'tools/@angular/tsc-wrapped/src/compiler_host.ts(55,32): error TS2345: Argument of type 'ts.Program' is not assignable to parameter of type 'ts.Program'.'
Could you help me?

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