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

Angular Performance Checklist

Angular Performance Checklist

High performance applications always bring better user engagement and experience. We often implicitly judge the quality of given application by it’s initial load time and responsiveness. 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 top of that, performing change detection over the entire component tree, corresponding to a complex UI, often causes frame drops because of heavy computations happening in the main thread. In the first part of this talk we’re going explain essential practices that can help us reduce the initial load time of our Angular applications.

Minko Gechev

October 21, 2016
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. Angular Performance Checklist
    Minko Gechev
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide

  2. github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com
    About
    deprecated

    View Slide

  3. View Slide

  4. Hold on for the
    second edition!

    View Slide

  5. Inspirational thought about programming
    https://flic.kr/p/bmgpJT
    Agenda
    Network performance
    Runtime performance

    View Slide

  6. Inspirational thought about programming
    https://flic.kr/p/bmgpJT
    Agenda
    Network performance
    Runtime performance

    View Slide

  7. Network Performance

    View Slide

  8. • Service Workers
    • Bundling
    • Minification & Dead code elimination
    • Tree-shaking
    • AoT compilation
    • Compression
    Network Performance

    View Slide

  9. View Slide

  10. Build a “Hello world” Angular 2 Application
    with minimal size

    View Slide

  11. Build a “Hello world” Angular 2 Application
    with minimal size

    View Slide

  12. View Slide

  13. “Hello world” in Angular 2

    View Slide














  14. index.html

    View Slide














  15. index.html

    View Slide

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

    View Slide

  17. 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

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

    View Slide

  19. View Slide

  20. Service Workers

    View Slide

  21. - Jake Archibald
    “ServiceWorker is a background worker, it
    gives us a JavaScript context to add features
    such as push messaging, background sync,
    geofencing and network control.”

    View Slide

  22. - Jake Archibald
    “ServiceWorker is a background worker, it
    gives us a JavaScript context to add features
    such as push messaging, background sync,
    geofencing and network control.”

    View Slide

  23. Service Worker
    Network

    View Slide

  24. Service Worker
    Network
    GET /i.gif

    View Slide

  25. Service Worker
    Network
    GET /i.gif

    View Slide

  26. Service Worker
    Network

    View Slide

  27. Service Worker
    Network

    View Slide

  28. We can think of service workers as
    proxy in the browser

    View Slide

  29. index.html


    ...

    ...
    <br/>navigator.serviceWorker<br/>.register('cache-network.sw.js');<br/>


    View Slide

  30. cache-network.sw.js
    importScripts('./system-config.js');
    self.addEventListener('install', event => {
    event.waitUntil(
    caches.open('mysite-v3').then(cache =>
    cache.addAll([
    'zone.js'
    // etc
    ])));
    });
    self.addEventListener(‘fetch', event => {
    event.respondWith(caches
    .match(event.request).then(response =>
    response || fetch(event.request)));
    });

    View Slide

  31. cache-network.sw.js
    importScripts('./system-config.js');
    self.addEventListener('install', event => {
    event.waitUntil(
    caches.open('mysite-v3').then(cache =>
    cache.addAll([
    'zone.js'
    // etc
    ])));
    });
    self.addEventListener('fetch', event => {
    event.respondWith(caches
    .match(event.request).then(response =>
    response || fetch(event.request)));
    });

    View Slide

  32. cache-network.sw.js
    importScripts('./system-config.js');
    self.addEventListener('install', event => {
    event.waitUntil(
    caches.open('mysite-v3').then(cache =>
    cache.addAll([
    'zone.js'
    // etc
    ])));
    });
    self.addEventListener('fetch', event => {
    event.respondWith(caches
    .match(event.request).then(response =>
    response || fetch(event.request)));
    });

    View Slide

  33. cache-network.sw.js
    importScripts('./system-config.js');
    self.addEventListener('install', event => {
    event.waitUntil(
    caches.open('mysite-v3').then(cache =>
    cache.addAll([
    'zone.js'
    // etc
    ])));
    });
    self.addEventListener('fetch', event => {
    event.respondWith(caches
    .match(event.request).then(response =>
    response || fetch(event.request)));
    });

    View Slide

  34. Angular Mobile Toolkit
    makes it easier

    View Slide

  35. index.html


    ...

    <br/>navigator.serviceWorker<br/>.register('/worker-basic.js');<br/>


    View Slide

  36. ngsw-manifest.json
    {
    "static": {
    "urls": {
    "zone.js": "00ea1da4192a2030f9ae023de3b3143ed647bbab",
    "Reflect.js": "c48ea7a8ab46d67174f708d2464a46aa523d218d",
    "bundle.js": "25afd671e9ad333887e2ef79b34297125a6a0b6a"
    },
    "_generatedFromWebpack": true
    }
    }

    View Slide

  37. You can
    generate the manifest
    with webpack!

    View Slide

  38. Bundling

    View Slide

  39. “…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

  40. View Slide

  41. View Slide

  42. Less requests,
    so latency has smaller impact

    View Slide

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

    View Slide

  44. …one big blocking request

    View Slide

  45. …one big blocking request

    View Slide

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

    View Slide

  47. “Hello world” app in Angular 2

    View Slide

  48. “Hello world” app in Angular 2

    View Slide

  49. Minification & Dead Code Elimination

    View Slide

  50. 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

  51. Dead code elimination

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. Usually we can get rid of
    small portions
    of unused code

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. View Slide

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

    View Slide

  62. Bundling CommonJS Modules

    View Slide

  63. module.exports.pow =
    (num, pow) => Math.pow(num, pow);
    module.exports.log2 =
    num => Math.log2(num);
    index.js
    math.js
    const pow = require('./math').pow;
    console.log(pow(2, 3));

    View Slide

  64. module.exports.pow =
    (num, pow) => Math.pow(num, pow);
    module.exports.log2 =
    num => Math.log2(num);
    index.js
    math.js
    const pow = require('./math').pow;
    console.log(pow(2, 3));

    View Slide

  65. module.exports.pow =
    (num, pow) => Math.pow(num, pow);
    module.exports.log2 =
    num => Math.log2(num);
    index.js
    math.js
    const pow = require('./math').pow;
    console.log(pow(2, 3));

    View Slide

  66. bundle.js
    module.exports.pow =
    (num, pow) => Math.pow(num, pow);
    module.exports.log2 =
    num => Math.log2(num);
    const pow = require('./math').pow;
    console.log(pow(2, 3));

    View Slide

  67. Why is that?

    View Slide

  68. bundle.js
    const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./math')[funcName];
    console.log(fn(2, 42));

    View Slide

  69. bundle.js
    const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./math')[funcName];
    console.log(fn(2, 42));

    View Slide

  70. bundle.js
    const readFile =
    require('fs').readFileSync;
    const funcName =
    readFile('name.txt')
    .toString().trim();
    const fn = require('./math')[funcName];
    console.log(fn(2, 42));

    View Slide

  71. CommonJS modules are
    Hard for Static Code Analysis because
    they are dynamic

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  79. 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

  80. Tree-shaking

    View Slide

  81. …dropping unused exports

    View Slide

  82. Tooling
    • Rollup.js
    • Supports ES2015 modules
    • Has plugin for commonjs
    • Webpack 2
    • Performs tree-shaking over ES2015
    • Google Closure Compiler
    • Limited ES2015 modules support
    • Can use TypeScript type annotations with tsickle

    View Slide

  83. Lets roll it up!

    View Slide

  84. $ 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

  85. $ 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

  86. $ 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

  87. $ 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

  88. $ 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

  89. View Slide

  90. tree-shaking
    templates

    View Slide



  91. Existing users:

    Login here


    How many directives do we have here?

    View Slide



  92. Existing users:

    Login here


    How many directives do we have here?

    View Slide

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

    View Slide



  94. Existing users:

    Login here


    How many directives do we have here?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  98. We Cannot Truly Tree-Shake an Angular
    Application?

    View Slide

  99. …of course we can…

    View Slide

  100. Introducing
    ngc

    View Slide



  101. Existing users:

    Login here


    View Slide

  102. 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

  103. 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

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

    View Slide

  105. AoT 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

  106. $ ./node_modules/.bin/ngc -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

  107. $ ./node_modules/.bin/ngc -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

  108. $ ./node_modules/.bin/ngc -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

  109. $ ./node_modules/.bin/ngc -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

  110. View Slide

  111. 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

  112. Compression

    View Slide

  113. Client Server

    View Slide

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

    View Slide

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

    View Slide

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

    bro --decompress app.js

    View Slide

  117. 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

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

    View Slide

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

    View Slide

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

    View Slide

  121. View Slide

  122. 36x smaller
    compared to the initial bundle!

    View Slide

  123. View Slide

  124. …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

  125. Resources:
    • mgv.io/ng-perf-checklist
    • mgv.io/build-ng-prod
    • mgv.io/mb-toolkit
    • github.com/angular/angular-cli
    • mgv.io/ng-seed

    View Slide

  126. mgv.io/perf-checklist-feedback

    View Slide

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

    View Slide