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. • Service Workers • Bundling • Minification & Dead code

    elimination • Tree-shaking • AoT compilation • Compression Network Performance
  2. <!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
  3. <!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
  4. 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
  5. main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule

    } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
  6. - 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.”
  7. - 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.”
  8. index.html <!DOCTYPE html> <html lang="en"> ... <body> ... <script> navigator.serviceWorker

    .register('cache-network.sw.js'); </script> </body> </html>
  9. 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))); });
  10. 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))); });
  11. 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))); });
  12. 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))); });
  13. “…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”
  14. 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. dummy.js function answer() { const expr = true; if (expr)

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

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

    return 42; } return 1.618; } console.log(answer()); dummy.js
  18. 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));
  19. 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));
  20. 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));
  21. 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));
  22. foo.js index.js export foo = () => 'foo'; export bar

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

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

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

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

    => 'bar'; import { foo } from `./${foo}`; console.log(foo()); foo.js index.js
  27. 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. 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
  29. $ 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. $ 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. $ 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. $ 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. $ 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. 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="bp-logo"></div> <div *ngIf="showLogin" class="login"> Existing users: <a href="#" [routerLink]="['login']"> Login here </a> </div>
  35. 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="bp-logo"></div> <div *ngIf="showLogin" class="login"> Existing users: <a href="#" [routerLink]="['login']"> Login here </a> </div>
  36. 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
  37. $ ./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
  38. $ ./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
  39. $ ./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
  40. $ ./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
  41. Disclaimer ngc will not always decrease your bundle size. For

    large to medium applications the produced from the compilation JavaScript increases the app size.
  42. Client Server GET /app.js Accept-Encoding: deflate, br HTTP/1.1 200 OK

    Content-Encoding: br … bro --decompress app.js
  43. 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
  44. …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