Building an Angular App for Production

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=47 Minko Gechev
November 11, 2016

Building an Angular App for Production

In the world of the single-page applications we usually have to transfer huge amount of resources over the wire which dramatically impacts the initial load time. On the other hand, high-performance applications always bring better user engagement and experience. We often implicitly judge the quality of given app by it’s initial load time and responsiveness.

In this talk we’re going explain essential practices that can help us reduce the initial load time of our Angular applications. We will go through bundling, dead-code elimination, tree-shaking and complete our journey with Ahead-of-Time compilation. We're going to explore "How we can make Angular small again"!

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

November 11, 2016
Tweet

Transcript

  1. 2.
  2. 3.
  3. 5.
  4. 6.
  5. 8.

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body>

    <my-app></my-app> <script src="/node_modules/zone.js/dist/zone.js"></script> <script src="/node_modules/reflect-metadata/Reflect.js"></script> <script src="dist/bundle.js"></script> </body> </html> index.html
  6. 9.

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body>

    <my-app></my-app> <script src="/node_modules/zone.js/dist/zone.js"></script> <script src="/node_modules/reflect-metadata/Reflect.js"></script> <script src="dist/bundle.js"></script> </body> </html> index.html
  7. 10.
  8. 11.

    import { NgModule } from '@angular/core'; import { BrowserModule }

    from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], bootstrap: [AppComponent], declarations: [AppComponent], }) export class AppModule {} app.module.ts
  9. 12.

    main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule

    } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
  10. 13.
  11. 14.

    “…standard practice aiming to reduce the number of requests that

    the browser needs to perform in order to deliver the application requested by the user”
  12. 15.
  13. 16.
  14. 27.

    Minification & dead code elimination • Mangling • Rename variable

    names • Rename property names • others… • Compression • Dead code elimination • Compress property access • Compress expressions • Pure functions • others…
  15. 29.

    dummy.js function answer() { const expr = true; if (expr)

    { return 42; } return 1.618; } console.log(answer());
  16. 30.

    function answer() { const expr = true; if (true) {

    return 42; } return 1.618; } console.log(answer()); dummy.js
  17. 31.

    function answer() { const expr = true; if (true) {

    return 42; } return 1.618; } console.log(answer()); dummy.js
  18. 37.
  19. 40.

    index.js lib.js module.exports.foo = () => 'foo'; module.exports.bar = ()

    => 'bar'; const foo = require('./lib').foo; console.log(foo());
  20. 41.

    index.js module.exports.foo = () => 'foo'; module.exports.bar = () =>

    'bar'; lib.js const foo = require('./lib').foo; console.log(foo());
  21. 43.

    bundle.js module.exports.foo = () => 'foo'; module.exports.bar = () =>

    'bar'; const foo = require('./lib').foo; console.log(foo());
  22. 50.

    lib.js index.js export foo = () => 'foo'; export bar

    = () => 'bar'; import { foo } from './lib'; console.log(foo());
  23. 51.

    export foo = () => 'foo'; export bar = ()

    => 'bar'; import { foo } from './lib'; console.log(foo()); index.js lib.js
  24. 52.

    export foo = () => 'foo'; export bar = ()

    => 'bar'; import { foo } from './lib'; console.log(foo()); index.js lib.js
  25. 53.

    export foo = () => 'foo'; export bar = ()

    => 'bar'; import { foo } from `./${name}`; console.log(foo()); index.js lib.js
  26. 54.

    export foo = () => 'foo'; export bar = ()

    => 'bar'; import { foo } from `./${name}`; console.log(foo()); index.js lib.js
  27. 56.

    ES2015 modules • Exports are always static • Identifiers •

    Default exports • Imports are always static • Strings as paths • Importing specific symbols referenced by their identifiers • Wildcard (the entire module) • Importing the default export
  28. 59.

    Tooling • Rollup.js • Supports ES2015 modules • Has plugin

    for commonjs • Webpack 2 • Performs tree-shaking over ES2015 • Google Closure Compiler • Limited ES2015 modules support
  29. 61.

    $ rollup -c config.js -o bundle.es2015.js $ tsc --target es5

    --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 502K Oct 15 18:41 bundle.min.js
  30. 62.

    $ rollup -c config.js -o bundle.es2015.js $ tsc --target es5

    --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 502K Oct 15 18:41 bundle.min.js
  31. 63.

    $ rollup -c config.js -o bundle.es2015.js $ tsc --target es5

    --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 502K Oct 15 18:41 bundle.min.js
  32. 64.

    $ rollup -c config.js -o bundle.es2015.js $ tsc --target es5

    --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 502K Oct 15 18:41 bundle.min.js
  33. 65.

    $ rollup -c config.js -o bundle.es2015.js $ tsc --target es5

    --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 502K Oct 15 18:41 bundle.min.js
  34. 66.
  35. 73.
  36. 79.

    import { NgIf, AppElement, TemplateRef_ } from "…"; ... this._anchor_11

    = r.createTemplateAnchor(this._el_5,(null as any)); this._appEl_11 = AppElement(11,5,this,this._anchor_11); this._TemplateRef_11_5 = TemplateRef_(this._appEl_11, HeaderComponent1); this._NgIf_11_6 = NgIf(this._appEl_11.vcRef, this._TemplateRef_11_5); this._text_12 = r.createText(this._el_5,'\n ',(null as any)); ... <div class="hero"></div> <div *ngIf="showLogin" class="login"> Existing users: <a href="#" [routerLink]="['login']"> Login here </a> </div>
  37. 80.

    import { NgIf, AppElement, TemplateRef_ } from "…"; ... this._anchor_11

    = r.createTemplateAnchor(this._el_5,(null as any)); this._appEl_11 = AppElement(11,5,this,this._anchor_11); this._TemplateRef_11_5 = TemplateRef_(this._appEl_11, HeaderComponent1); this._NgIf_11_6 = NgIf(this._appEl_11.vcRef, this._TemplateRef_11_5); this._text_12 = r.createText(this._el_5,'\n ',(null as any)); ... <div class="hero"></div> <div *ngIf="showLogin" class="login"> Existing users: <a href="#" [routerLink]="['login']"> Login here </a> </div>
  38. 82.

    AoT compilation Brings • Easy drop of unused components in

    bundle • Code which is easy for dead-code elimination • Fast initial rendering • Components are distributed pre-compiled • Allows us to drop compiler from bundler • We compile ahead of time so we don’t need it runtime
  39. 83.

    $ ./node_modules/.bin/ngc -p tsconfig.json $ tsc -p tsconfig.json $ rollup

    -c config.js -o bundle.es2015.js $ tsc --target es5 --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 210K Oct 15 18:41 bundle.min.js
  40. 84.

    $ ./node_modules/.bin/ngc -p tsconfig.json $ tsc -p tsconfig.json $ rollup

    -c config.js -o bundle.es2015.js $ tsc --target es5 --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 210K Oct 15 18:41 bundle.min.js
  41. 85.

    $ ./node_modules/.bin/ngc -p tsconfig.json $ tsc -p tsconfig.json $ rollup

    -c config.js -o bundle.es2015.js $ tsc --target es5 --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 210K Oct 15 18:41 bundle.min.js
  42. 86.

    $ ./node_modules/.bin/ngc -p tsconfig.json $ tsc -p tsconfig.json $ rollup

    -c config.js -o bundle.es2015.js $ tsc --target es5 --allowJs bundle.es2015.js $ uglifyjs bundle.js --output bundle.min.js $ ls bundle.min.js 210K Oct 15 18:41 bundle.min.js
  43. 87.
  44. 88.

    Disclaimer ngc will not always decrease your bundle size. For

    large to medium applications the produced from the compilation JavaScript increases the app size.
  45. 93.

    Client Server GET /app.js Accept-Encoding: deflate, br HTTP/1.1 200 OK

    Content-Encoding: br … bro --decompress app.js
  46. 94.

    Popular algorithms • Deflate • Widely supported algorithm • Gzip

    + checksum in header/footer • Brotli • Similar runtime performance compared to deflate • Provides more dense compression compared to deflate • Not widely supported
  47. 98.
  48. 102.

    /** * @param {string} name * @return {Superhero} */ function

    heroFactory(name) { return new Superhero(name, 3); } hero.ts
  49. 103.

    $ tsickle function heroFactory(name: string) { return new Superhero(name, 3);

    } /** * @param {string} name * @return {Superhero} */ function heroFactory(name) { return new Superhero(name, 3); }
  50. 104.

    $ tsickle function heroFactory(name: string) { return new Superhero(name, 3);

    } /** * @param {string} name * @return {Superhero} */ function heroFactory(name) { return new Superhero(name, 3); }
  51. 105.

    $ tsickle function heroFactory(name: string) { return new Superhero(name, 3);

    } /** * @param {string} name * @return {Superhero} */ function heroFactory(name) { return new Superhero(name, 3); }
  52. 109.
  53. 110.

    Tools we used • tsc • Browserify • UglifyJS •

    Rollup.js • ngc • Google Closure Compiler • Brotli
  54. 111.
  55. 112.
  56. 114.
  57. 115.
  58. 116.
  59. 117.
  60. 118.
  61. 119.
  62. 120.

    …all this in angular-cli # build an app with env

    config ng build --prod --env=prod …in angular-seed # build with AoT npm run build.prod.exp