mgechev / angular-seed

🌱 [Deprecated] Extensible, reliable, modular, PWA ready starter project for Angular (2 and beyond) with statically typed build and AoT compilation
https://mgechev.github.io/angular-seed
MIT License
4.57k stars 1.45k forks source link

Advanced production build #864

Closed mgechev closed 7 years ago

mgechev commented 8 years ago

Currently our production build is quite basic and produces only two bundles:

Which means that we're not taking advantage of:

Incrementally improve the production build by:

Step 1

hmagrini commented 8 years ago

Would Critical Path CSS be something interesting to generate as well?

phiphou commented 8 years ago

As I said in #860 , application (only the code you/we wrote), vendors (angular2, rxjs and potential additional vendors like jquery, lodash...), and polyfills (reflect-metadata, zone, es6-shim,...) life-cycles aren't the same most of time. Keeping them in separate bundles seems to me a good practice. I'll be interested in knowing your thoughts about this point

I also agree lazy loading would be a great feature. It would obviously augment the amount of generated bundles, but I don't see any major reason not to working this way.

Bigous commented 8 years ago

Lazy loading and hot loading are amazing. We could create a pattern ... I though, when it was changed, that components that started with + in it's directory would be lazy loaded only when needed. I like this pattern, what do you think?

jlberrocal commented 8 years ago

@Bigous i'm using the + for indicate that is a lazy loaded component, for development seems to work fine, but for production there is still only 2 JS files, one with the shims and a big one with the vendors and the app itself

Bigous commented 8 years ago

Yes thats why I think we could create a name pattern for that. Bundle the components in other files, like AngularClass did with the webpack starter kit would be a nice one and would resolve the step 2. Unfortunately I've got almost no time during the past 3 months to help with the project, but the idea is still very valid.

mgechev commented 8 years ago

Here's a blog post I wrote about an experiment I did around the production build efficiency.

A working "Hello world!" example with precompilation + tree-shaking, can be found here.

mgechev commented 8 years ago

In addition - I'll be playing with the production build to introduce:

These tools should decrease the bundle size to around 20k.

Bigous commented 8 years ago

20k footprint for ng2 is unbelievable! Awesome... and congratulations for the great article!

vyakymenko commented 8 years ago

@mgechev , I'm not sure about Google Clojure Compiler, because there are so many real testing information about GCC vs UglyfyJS2.

https://www.peterbe.com/plog/advanced-closure-compiler-vs-uglifyjs2

But, it's a good point to test it with advanced flag.

mgechev commented 8 years ago

Update: It is still too early to use offline compilation. The platform directives need to be explicitly configured and also I had issues making it work when ngModel directive is used.

In general, my plan is to implement a task called build.prod.exp which is going to be an experimental production build which includes everything from above.

SekibOmazic commented 8 years ago

@mgechev When could we expect tree-shaking with rollup.js? Any timeline on this?

mgechev commented 8 years ago

@SekibOmazic there's WIP in this branch https://github.com/mgechev/angular2-seed/tree/advanced-build

SekibOmazic commented 8 years ago

@mgechev cool! Hey did you just add rollup.js?

mgechev commented 8 years ago

Yes only rollup. There are some rxjs resolution issues that need to be addressed + refactoring, before being able to merge in master.

SekibOmazic commented 8 years ago

@mgechev running gulp build.prod.exp creates a tmp/app.js of 1.6M. It doesn't look really "squeezed". Am I missing something?

mgechev commented 8 years ago

There's no minification and compression of the code, as I already said the work in this branch is Work In Progress.

ZuSe commented 8 years ago

I also would like to see support for cloud foundry/heroku deployment. Right now a lot of unnecessary files is shipped.

Perezmarc commented 8 years ago

Are you supporting gzipped build at the moment? my app.js file is 2.5mb and my project is not that huge. On production it is 1.6 s to load for the first time...

vyakymenko commented 8 years ago

@Perezmarc , gzipped build o_O, what do you mean when you talking about gzipped build ?

Gzip is using on server-side And you configure level of gzip compression as you want.

Perezmarc commented 8 years ago

sorry for my ignorance, looked at the post http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/ and as build.prod currently generates a minified code, I thought that the next step for reducing the load time was gzipping it. :S I can't generate then a build to deploy it to S3 gzipped? In order to gzip it, what should i do?

vyakymenko commented 8 years ago

@Perezmarc , for example, if you using NginX for representing your application, you can look for my example for configuring gzip compression here.

But, I'm still confusing, what do you mean about deploy gzipped code. I think you need more documentation and literature about how it's work.

Perezmarc commented 8 years ago

I thought a gzipped code could be deployed somehow and reduce load time, but probably it makes no sense... Probably my only option is to use angular-universal and render on server to reduce that 1.6 seconds? and lazy loading.

I found this article: http://blog.angular-university.io/angular-2-universal-meet-the-internet-of-the-future-seo-friendly-single-page-web-apps/ I was about to implement the prerendering on S3. That should reduce load time, as I understand

mgechev commented 8 years ago

FYI I just added experimental support of AoT compilation support https://github.com/mgechev/angular2-seed/commit/bf5a3322329ecc603ea97d101da79f789cb3d70b. It'll be great to test it and once we feel that it is stable enough, merge with the master branch.

mgechev commented 8 years ago

Also keep in mind that it may not work on Windows https://github.com/angular/angular/issues/8582.

SekibOmazic commented 8 years ago

@mgechev It works fine on Mac, no problems so far. Bundle size is now 1.1M (minified but not gzipped) which is still quite big. Will this be further optimised?

Another proposal: create 3 bundles in prod build: shims, vendor (angular) and app.

mgechev commented 8 years ago

The ahead of time compilation doesn't reduce the bundle size. Once the seed includes Google Closure Compiler we can drop the size more than 10 times, thanks fo tree-shaking and compression (compression we can use today as well).

sfabriece commented 8 years ago

Shouldn't it reduce size due to the compiler not being included?

vyakymenko commented 8 years ago

@sfabriece , don't forget about gzip.

mgechev commented 8 years ago

@sfabriece correct, which is still significant improvement. By this we get 0.5M off - 1.6M -> 1.1M.

Today I'll work on integration of Google Closure Compiler which should reduce the size even more, although we won't be able to perform tree-shaking yet.

mgechev commented 8 years ago

FYI we now have Google Closure Compiler support https://github.com/mgechev/angular2-seed/commit/e8105f1627a752b2bae2d1d575a65beecf982c8c. I'd recommend you to give it a try and let me know if you face any issues. The entire JavaScript payload now is ~170K and will drop dramatically once GCC supports export *.

mpetkov commented 8 years ago

Hey all, I just read through this entire thread and other threads about lazy-loading/async routing. Wanted to find out if there is any timeframe on when it will be implemented?

I am currently working on application that has a single codebase but many seprate screens (routes). Currently the one prod bundle is getting too big so I am forced to create separate projects for each route to improve loading performance.

mgechev commented 8 years ago

@mpetkov the first step is to reduce the size of the production build. Currently it's 150k (gzipped), it should drop to ~50-70K once we get the tree-shaking work properly.

ruffiem commented 8 years ago

@mgechev the output size is quite big indeed... What is wrong with the tree-shaking ?

mgechev commented 8 years ago

We can provide solution for tree-shaking which works in the general case because a lot of projects use commonjs modules. Although there's a commonjs plugin for rollup it might get tricky to configure it for the variety of packages available out there. Google Closure Compiler seems like a good option which can provide bundling, minification and tree-shaking in a single step but we're still blocked by their lack of support for export *.

ruffiem commented 8 years ago

So either we should define a standard use of commonjs modules or we wait for the Closure Compiler to implement the export wildcard. Just ran a few test using the Closure Compiler and faced input0:2: ERROR - ES6 transpilation of 'Wildcard export' is not yet implemented.. But that's good news, it says 'not yet implemented'.

ruffiem commented 8 years ago

We'll maybe get something before the end of the year... https://github.com/google/closure-compiler/issues/878

mo-ba commented 8 years ago

imho tree shaking is a nice (to have) feature, but in terms of speed lazy loading gives a bigger boost, especially as applications get bigger... is there any reason why you want to implement it in that order?

mgechev commented 8 years ago

@mo-ba you can take a look at this issue. Lazy-loading seems possible at this point https://github.com/mgechev/angular2-seed/issues/1358.

mgechev commented 8 years ago

Today I did some experiments with rollup.js and was able to reduce the size of the production bundle from 131K to 86K (gzipped & minified). At first this sounds really attractive, however, the consumption of cjs modules can get tricky.

My suggestion is to implement an experimental bundling with tree-shaking, taking advantage of rollup, keep the default build.prod & build.prod.exp the way they are and introducing a new one build.prod.bundle.exp which is basically build.prod.exp but instead of SystemJS Builder, for bundling we'll use rollup. This way only people who know what they want to do will be able to reduce the bundle size of their apps even further.

What do you think?

Bigous commented 8 years ago

Hi @mgechev, Ionic v2 RC0 is using rollup and it's a pain to add external libraries. Clojure was our another option... I like to keep the build.prod in safe mode :D and add a build.prod.exp with rollup or clojure.

ruffiem commented 8 years ago

@mgechev - If Closure cause less trouble consuming cjs modules, we should maybe wait until the export wildcard is available. In the meantime, experimenting around rollup is very appreciated though : 86k is sick but what does it include ?

@Bigous - what kind of support do we have to import external libs with rollup ? rollup keeps them out the bundle ?

Bigous commented 8 years ago

@ruffiem If the library was not developed to be tree shake-able, like libraries that are just a wrapper to other library - lodash, highcharts, charts, etc... - you need to provide a custom rollup settings. But as it is now, it's just an npm install and you are good to go. We already have to do that in our seed, because we use SystemJS, so... it would not be a game break, but I like to keep things simple. You can see the ionic discussion driftyco/ionic-app-scripts#68 . It was closed, but still not resolved and people are moving back to version beta.11 so they can avoid rollup config...

mgechev commented 8 years ago

FYI I'm experimenting with lazy loading here https://github.com/mgechev/angular-seed/tree/lazy so we may have something working by the end of the week.

RoxKilly commented 8 years ago

About Closure's incompatibility: Angular gave a presentation at AngularConnect 2016 that suggested tsickle as a bridge to transpile ng2 typescript to Closure compatible JS. See this video from 23:15 to 24:10.

Is that info relevant to this discussion?

mgechev commented 8 years ago

Yes, I think running closure build with preprocessed with tsickle code is possible. I have played with tsickle recently and given that it's wrapper around tsc the integration with the seed should be relatively straightforward.

Here's a post about my experience http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/.

In the "lazy" branch I am also playing with lazy loading which given the bundle arithmetic of SystemJS builder should not be that hard to implement neither. I am not sure how much time I will be able to spend on this in the next a couple of days/by the end of the month.

If anyone is interested in implementing any of these tasks we can chat and I can share some pointers.

mgechev commented 8 years ago

Btw, from both tasks, lazy loading seems higher priority, easier to implement and won't vary much in near future.

SamVerschueren commented 8 years ago

I'm looking into the lazy loading part with what you have done so far. I always get this error message

Cannot find 'default' in 'app/+foo/foo.module.js'

I also tried it without export default and attaching #FooModule at the end of the path, but still no dice. Anyone who knows more about this issue?

RoxKilly commented 8 years ago

@SamVerschueren do you have an import statement without brackets anywhere? eg:

import MyModule from 'app/my.module';

Should be

import {MyModule} from 'app/my.module';
SamVerschueren commented 8 years ago

@RoxKilly With lazy loading, you don't have things like import * from 'app/my.module' because that will actually already import the code and that would mean you can't break those pieces into 2.

This means you have something like this

@NgModule({})
export default FooModule { }
const routes: Routes = [
    { path: 'foo', loadChildren: 'app/+foo/foo.module' }
];

This results in the error

Cannot find 'default' in 'app/+foo/foo.module.js'

Someone at the Angular2 gitter said that export default does not work with AoT. Not sure about that though, but in order to make sure it works I tried it like this.

@NgModule({})
export FooModule { }
const routes: Routes = [
    { path: 'foo', loadChildren: 'app/+foo/foo.module#FooModule' }
];

Which results in

Cannot find 'FooModule' in 'app/+foo/foo.module.js'

I already read through this thread and tried most of the suggestions, no dice.

RoxKilly commented 8 years ago

@SamVerschueren

export FooModule { }

Should be

export class FooModule {}