Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building an Angular App for Production

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"!

Minko Gechev

November 11, 2016
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. Building an Angular App for Production
    Minko Gechev
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide

  2. View Slide

  3. View Slide

  4. Hold on for the
    second edition!

    View Slide

  5. View Slide

  6. Inspirational thought about programming
    Agenda
    • Bundling
    • Minification
    • Tree-shaking
    • AoT compilation
    • Compression

    View Slide

  7. Building “Hello world” in Angular 2

    View Slide














  8. index.html

    View Slide














  9. index.html

    View Slide

  10. app.component.ts
    import { Component } from '@angular/core';
    @Component({
    selector: 'my-app',
    template: 'Hello world!'
    })
    export class AppComponent {}

    View Slide

  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

    View Slide

  12. main.ts
    import { platformBrowserDynamic } from
    '@angular/platform-browser-dynamic';
    import { AppModule } from './app.module';
    platformBrowserDynamic().bootstrapModule(AppModule);

    View Slide

  13. Bundling

    View Slide

  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”

    View Slide

  15. View Slide

  16. View Slide

  17. Less requests
    so our application gets faster

    View Slide

  18. $ browserify -s main main.js > bundle.js

    View Slide

  19. …one big blocking request

    View Slide

  20. …one big blocking request

    View Slide

  21. …okay but
    that’s a “Hello world” app

    View Slide

  22. “Hello world” app in Angular 2

    View Slide

  23. “Hello world” app in Angular 2

    View Slide

  24. Building “Hello world” in Angular 2
    with minimal size

    View Slide

  25. Building “Hello world” in Angular 2
    with minimal size

    View Slide

  26. Minification & Dead Code Elimination

    View Slide

  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…

    View Slide

  28. Dead code elimination

    View Slide

  29. dummy.js
    function answer() {
    const expr = true;
    if (expr) {
    return 42;
    }
    return 1.618;
    }
    console.log(answer());

    View Slide

  30. function answer() {
    const expr = true;
    if (true) {
    return 42;
    }
    return 1.618;
    }
    console.log(answer());
    dummy.js

    View Slide

  31. function answer() {
    const expr = true;
    if (true) {
    return 42;
    }
    return 1.618;
    }
    console.log(answer());
    dummy.js

    View Slide

  32. console.log(42);
    dummy.js

    View Slide

  33. …usually we can get rid of
    small portions
    of unused code

    View Slide

  34. $ uglifyjs bundle.js --output bundle.min.js
    $ ls bundle.min.js
    524K Oct 15 18:41 bundle.min.js

    View Slide

  35. $ uglifyjs bundle.js --output bundle.min.js
    $ ls bundle.min.js
    524K Oct 15 18:41 bundle.min.js

    View Slide

  36. $ uglifyjs bundle.js --output bundle.min.js
    $ ls bundle.min.js
    524K Oct 15 18:41 bundle.min.js

    View Slide

  37. View Slide

  38. 3x smaller
    but it’s still huge…

    View Slide

  39. Bundling CommonJS Modules

    View Slide

  40. index.js
    lib.js
    module.exports.foo = () => 'foo';
    module.exports.bar = () => 'bar';
    const foo = require('./lib').foo;
    console.log(foo());

    View Slide

  41. index.js
    module.exports.foo = () => 'foo';
    module.exports.bar = () => 'bar';
    lib.js
    const foo = require('./lib').foo;
    console.log(foo());

    View Slide

  42. index.js
    const foo = require('./lib').foo;
    console.log(foo());
    lib.js
    module.exports.foo = () => 'foo';
    module.exports.bar = () => 'bar';

    View Slide

  43. bundle.js
    module.exports.foo = () => 'foo';
    module.exports.bar = () => 'bar';
    const foo = require('./lib').foo;
    console.log(foo());

    View Slide

  44. Why is that?

    View Slide

  45. index.js
    const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./lib')[funcName];
    console.log(fn());

    View Slide

  46. const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./lib')[funcName];
    console.log(fn());
    index.js

    View Slide

  47. const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./lib')[funcName];
    console.log(fn());
    index.js

    View Slide

  48. CommonJS modules are
    hard for static code analysis because
    they are dynamic

    View Slide

  49. ES2015 modules are
    static so they can be
    statically analyzed easier

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. bundle.js
    let foo = () => 'foo';
    console.log(foo());

    View Slide

  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

    View Slide

  57. Tree-shaking

    View Slide

  58. …dropping unused exports

    View Slide

  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

    View Slide

  60. Lets roll it up!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  66. View Slide

  67. tree-shaking
    templates

    View Slide



  68. Existing users:

    Login here


    How many directives do we have here?

    View Slide



  69. Existing users:

    Login here


    How many directives do we have here?

    View Slide

  70. @Component({
    selector: 'div.hero',
    template: '{{ hero.name }}'
    })
    class SuperheroComponent {
    @Input() hero: Superhero;
    }
    superhero.component.ts

    View Slide



  71. Existing users:

    Login here


    How many directives do we have here?

    View Slide

  72. At Runtime we know which directives are
    used in our templates

    View Slide

  73. app.component.ts
    import { Component } from '@angular/core';
    @Component({
    selector: 'my-app',
    template: 'Hello world!'
    })
    export class AppComponent {}

    View Slide

  74. NgIf, NgFor and other
    useless directives are still in our bundle

    View Slide

  75. We cannot truly tree-shake an Angular
    Application?

    View Slide

  76. …of course we can…

    View Slide

  77. Introducing
    ngc

    View Slide



  78. Existing users:

    Login here


    View Slide

  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));
    ...


    Existing users:

    Login here


    View Slide

  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));
    ...


    Existing users:

    Login here


    View Slide

  81. Angular’s
    Ahead-of-Time (AoT)
    compilation

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  87. View Slide

  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.

    View Slide

  89. Compression

    View Slide

  90. Client Server

    View Slide

  91. Client Server
    GET /app.js
    Accept-Encoding: deflate, br

    View Slide

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

    View Slide

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

    bro --decompress app.js

    View Slide

  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

    View Slide

  95. $ bro --force --input b.js --output b.brotli
    $ ls b.brotli
    39K Oct 15 18:38 b.brotli

    View Slide

  96. $ bro --force --input b.js --output b.brotli
    $ ls b.brotli
    39K Oct 15 18:38 b.brotli

    View Slide

  97. $ bro --force --input b.js --output b.brotli
    $ ls b.brotli
    39K Oct 15 18:38 b.brotli

    View Slide

  98. View Slide

  99. 36x smaller
    compared to the initial bundle!

    View Slide

  100. lets try
    one more thing

    View Slide

  101. Google Closure Compiler

    View Slide

  102. /**
    * @param {string} name
    * @return {Superhero}
    */
    function heroFactory(name) {
    return new Superhero(name, 3);
    }
    hero.ts

    View Slide

  103. $ tsickle
    function heroFactory(name: string) {
    return new Superhero(name, 3);
    }
    /**
    * @param {string} name
    * @return {Superhero}
    */
    function heroFactory(name) {
    return new Superhero(name, 3);
    }

    View Slide

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

    View Slide

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

    View Slide

  106. tsickle transpiles
    TypeScript to Closure
    compatible annotations

    View Slide

  107. $ ls bundle.brotli
    39K Oct 15 18:38 b.brotli

    View Slide

  108. $ ls bundle.brotli
    32K Oct 15 18:38 bundle.brotli

    View Slide

  109. View Slide

  110. Tools we used
    • tsc
    • Browserify
    • UglifyJS
    • Rollup.js
    • ngc
    • Google Closure Compiler
    • Brotli

    View Slide

  111. View Slide

  112. View Slide

  113. Here
    Here
    Here

    View Slide

  114. View Slide

  115. View Slide

  116. View Slide

  117. View Slide

  118. View Slide

  119. View Slide

  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

    View Slide

  121. Resources
    • mgv.io/ng-perf-checklist
    • mgv.io/build-ng-prod
    • mgv.io/mb-toolkit
    • mgv.io/n2cli
    • mgv.io/ng-seed

    View Slide

  122. mgv.io/ng4prod-feedback

    View Slide

  123. Thank you!
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide