Internals of the Angular CLI

Internals of the Angular CLI

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

June 24, 2020
Tweet

Transcript

  1. @yourtwitter Internals of the Angular CLI Minko Gechev twitter.com/mgechev github.com/mgechev

    blog.mgechev.com
  2. twitter.com/mgechev

  3. @mgechev

  4. @mgechev Framework

  5. @mgechev Framework CLI Components

  6. @mgechev Framework CLI Components I18n Language service Router Animations Forms

    PWA HTTP More!
  7. @mgechev Framework CLI Components I18n Language service Router Animations Forms

    PWA HTTP More!
  8. None
  9. @mgechev Disclaimer You don’t need to know any of the

    following content to use the Angular CLI
  10. @mgechev Framework CLI Components I18n Language service Router Animations Forms

    PWA HTTP More!
  11. @mgechev Framework CLI Components I18n Language service Router Animations Forms

    PWA HTTP More!
  12. @mgechev @angular/cli

  13. @mgechev @angular/cli Builders Schematics

  14. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

  15. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

    webpack
  16. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

    webpack TypeScript terser
  17. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

    ngc webpack TypeScript terser
  18. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

    ngc webpack TypeScript terser
  19. twitter.com/ mgechev

  20. @yourtwitter $ ng new sample --collection=custom Creating a project

  21. @mgechev Creating a project • Reading schematics collection from disk

    • Creating an in-memory file system • Transformation of the files (placeholder replacement) • Committing files to disk
  22. @mgechev

  23. @mgechev

  24. @mgechev

  25. @mgechev Schematics could be used for… Scaffolding Updates NgAdd

  26. @mgechev Scaffolding Updates NgAdd Code transform

  27. @mgechev Scaffolding Updates NgAdd Code transform

  28. @mgechev Code transformation • Direct string replacements • Regular expressions

    • AST transformation
  29. String replacements @Component({ selector: '<%= selector %>' })

  30. String replacements @Component({ selector: '<%= selector %>' }) fileContent.replace('<%= selector

    %>', 'selector');
  31. String replacements @Component({ selector: '<%= selector %>' }) fileContent.replace('<%= selector

    %>', 'selector'); @Component({ selector: 'selector' })
  32. String replacements @Component({ selector: '<%= selector %>' }) fileContent.replace('<%= selector

    %>', 'selector'); @Component({ selector: 'selector' })
  33. Regex replacements @Component({ selector: '<%= selector %>' })

  34. Regex replacements @Component({ selector: '<%= selector %>' }) const data

    = {selector: 'app-component'}; fileContent.replace(/<%= (\w+) %>/, function(a, m) { return data[m]; });
  35. Regex replacements @Component({ selector: '<%= selector %>' }) @Component({ selector:

    'app-component' }) const data = {selector: 'app-component'}; fileContent.replace(/<%= (\w+) %>/, function(a, m) { return data[m]; });
  36. Regex replacements @Component({ selector: '<%= selector %>' }) @Component({ selector:

    'app-component' }) const data = {selector: 'app-component'}; fileContent.replace(/<%= (\w+) %>/, function(a, m) { return data[m]; });
  37. Regex replacements @ViewChild('foo') foo; @ViewChild('foo', { static: true }) foo;

  38. @mgechev Conditions • Should be a property decorator • The

    class should be a component • * Should be a symbol from Angular
  39. Regex replacements @ViewChild('foo') foo; @ViewChild('foo', { static: true }) foo;

  40. @mgechev Source code + Grammar

  41. @mgechev Source code + Grammar

  42. twitter.com/mgechev

  43. twitter.com/mgechev

  44. twitter.com/mgechev

  45. @mgechev

  46. twitter.com/mgechev

  47. @mgechev Introduction to compilers

  48. @mgechev Introduction to compilers …in 2-ish minutes

  49. @mgechev Disclaimer You don’t need to understand compilers to use

    the Angular CLI
  50. @mgechev COMPILER Input Output

  51. @mgechev COMPILER FRONT-END BACK-END Input Output

  52. @mgechev Input Output FRONT-END

  53. @mgechev FRONT-END LEXICAL ANALYSIS SYNTAX ANALYSIS … Input Output …

  54. @mgechev const product = a * b;

  55. @mgechev tokenize('const product = a * b;')

  56. @mgechev tokenize('const product = a * b;') [ { lexeme:

    'const', type: 'keyword' }, { lexeme: 'product', type: 'identifier' }, { lexeme: '=', type: 'operator' }, ... ]
  57. @mgechev parse(tokenize('...'))

  58. @mgechev parse(tokenize('...')) CONST PRODUCT * A B

  59. @mgechev AST CONST PRODUCT * A B parse(tokenize('...'))

  60. @Component({ selector: 'app-component', template: '...' }) class AppComponent { ...

    } CLASS DECORATOR OBJ LITERAL SELECTOR APP- COMPONENT TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  61. @mgechev Conditions • Should be a property decorator • The

    class should be a component • * Should be a symbol from Angular
  62. visitClass(node) { node.decorators.forEach(visitDecorator); } visitDecorator(node) { if (node.name === 'Component')

    { visitComponent(node.parent) } } visitComponent(class) { class.properties.forEach(prop => { if (prop.decorator && prop.decorator.name === 'ViewChild') { // } }); } AST visitor
  63. visitClass(node) { node.decorators.forEach(visitDecorator); } visitDecorator(node) { if (node.name === 'Component')

    { visitComponent(node.parent) } } visitComponent(class) { class.properties.forEach(prop => { if (prop.decorator && prop.decorator.name === 'ViewChild') { // } }); } Property decorator Should be inside a component AST visitor
  64. visitClass(node) { node.decorators.forEach(visitDecorator); } visitDecorator(node) { if (node.name === 'Component')

    { visitComponent(node.parent) } } visitComponent(class) { class.properties.forEach(prop => { if (prop.decorator && prop.decorator.name === 'ViewChild') { // } }); } Property decorator Should be inside a component AST visitor
  65. Property decorator Should be inside a component visitClass(node) { node.decorators.forEach(visitDecorator);

    } visitDecorator(node) { if (node.name === 'Component') { visitComponent(node.parent) } } visitComponent(class) { class.properties.forEach(prop => { if (prop.decorator && prop.decorator.name === 'ViewChild') { // } }); } AST visitor
  66. @mgechev Conditions • Should be a property decorator • The

    class should be a component • * Should be a symbol from Angular
  67. @mgechev Source code + Grammar + Type checker

  68. @mgechev Source code + Grammar + Type checker

  69. @yourtwitter // Get the type checker from the program const

    typeChecker = program.getTypeChecker(); // Get the type of the symbol const type = typeChecker.getTypeAtLocation(node);
  70. @mgechev v10 focus • Opt-in strict flag • Increase visibility

  71. @mgechev v10 focus • Opt-in strict flag • Increase visibility

  72. @mgechev Strict Mode Strict TypeScript type checking strictTemplates in Angular

    Opt-in ES5 support Opt-in CommonJS support Reduced side-effects
  73. @mgechev

  74. twitter.com/mgechev >15% of bugs detectable at build time

  75. @yourtwitter Strictness helps in Automated updates Compile-time optimizations Angular Universal

    Efficient prefetching Optimized i18n pipeline …
  76. @mgechev Scaffolding Updates NgAdd Code transform

  77. @mgechev Developer facing APIs

  78. @mgechev Scaffolding Updates NgAdd Code transform

  79. { "name": "my-package", "version": "1.0.0", "schematics": "./collection.json" } { "name":

    "my-package", "schematics": { "ng-add": { "factory": "./ng-add/index.js”, "schema": "ng-add/schema.json", "description": " ..." }, "ng-new": { "factory": "./ng-new/index.js", "schema": "./ng-new/schema.json", "description": " ..." } } } package.json collection.json
  80. ng-add / ng-new import {Tree} from '@angular-devkit/schematics'; export default function

    MySchematic(options: any) { return (tree: Tree) => { tree.create(options.path + '/hi', 'Hello world!'); return tree; }; }
  81. @mgechev Scaffolding Updates NgAdd Code transform

  82. { "name": "my-package", "version": "1.0.0", "ng-update": { "migrations": "migrations.json" }

    } { "schematics": { "v1": { "factory": "./v1", "version": "1.0", "description": " ..." }, "v2": { "factory": "./v2", "version": "2.0", "description": " ..." } } } package.json migrations.json
  83. @mgechev Mid-recap • Schematics used for code transform • Agnostic

    to the Angular CLI • Widely adopted in the open source community • Use semantic replacements using: • Language grammar • Language type system
  84. @mgechev Building Angular

  85. @mgechev @angular/cli Builders Schematics Jobs Footprints ng add ng update

    ngc webpack TypeScript terser
  86. @mgechev Angular CLI builders @angular/cli $ ng run [app]:[cmd] angular.json

    require(builder) builder execute ✨
  87. @yourtwitter $ ng run app:serve # ng serve $ ng

    run app:build # ng build Creating a project
  88. @mgechev Browser builder

  89. @mgechev Aggregate js file size in KB for angular.io builds

    Is --prod worth it? 5000 4000 3000 2000 1000 0 658 KB 5072 KB Production Development Almost 10x smaller!
  90. @mgechev ngc tsc opt wp terser tsc ngcc i18n ng-xi18n

  91. @mgechev ngc tsc opt wp terser tsc

  92. @mgechev // utils.js export const add = (a, b) =>

    a + b; export const subtract = (a, b) => a - b; /******/ (() => { // webpackBootstrap /******/ "use strict"; // CONCATENATED MODULE: ./utils.js ** const add = (a, b) => a + b; const subtract = (a, b) => a - b; // CONCATENATED MODULE: ./index.js ** const index_subtract = (a, b) => a - b; console.log(add(1, 2)); /******/ })(); // index.js import { add } from './utils'; const subtract = (a, b) => a - b; console.log(add(1, 2)); webpack bundling
  93. @mgechev // utils.js export const add = (a, b) =>

    a + b; export const subtract = (a, b) => a - b; /******/ (() => { // webpackBootstrap /******/ "use strict"; // CONCATENATED MODULE: ./utils.js ** const add = (a, b) => a + b; const subtract = (a, b) => a - b; // CONCATENATED MODULE: ./index.js ** const index_subtract = (a, b) => a - b; console.log(add(1, 2)); /******/ })(); // index.js import { add } from './utils'; const subtract = (a, b) => a - b; console.log(add(1, 2)); webpack bundling
  94. @mgechev ngc tsc opt wp terser tsc

  95. @filipematossilv @mgechev Terser • ES2015 version of UglifyJS • DCE

    using static analysis • Annotations for side-effect free function calls • Also does name mangling, inlining, whitespace removal • Does not understand module loading!
  96. @mgechev Dead Code Elimination

  97. @filipematossilv (() =>{"use strict";console.log(3)}) () input output /******/ (() =>

    { // webpackBootstrap /******/ "use strict"; // CONCATENATED MODULE: ./utils.js ** const add = (a, b) => a + b; const subtract = (a, b) => a - b; // CONCATENATED MODULE: ./index.js ** const index_subtract = (a, b) => a - b; console.log(add(1, 2)); /******/ })();
  98. @mgechev Purity

  99. @filipematossilv input output const getData = (a) => localStorage.get(a); getData(2,

    3); (o =>localStorage.get(2))() const getData = (a) => localStorage.get(a); /*@ __PURE __ */getData(2, 3); Empty
  100. @mgechev ngc tsc opt wp terser tsc

  101. @mgechev Ivy @Component({ selector: 'app', template: ' ...' }) class

    AppComponent { ... } app.component.js app.component.d.ts
  102. @Component({ selector: 'cmp', template: ` <span *ngIf="foo"> </span>` }) export

    class Cmp {} ... Cmp.ɵcmp = defineComponent({ type: Cmp, selectors: [['cmp']], template: function(fs, ctx) { if (fs & RenderFlags.Create) ...; if (fs & RenderFlags.Update) ...; }, directives: [NgIf] }); ngc
  103. @mgechev

  104. @mgechev ngc tsc opt wp terser tsc

  105. @filipematossilv @mgechev Build Optimizer • Part of Angular Devkit •

    Post processor for TS and Angular AOT code • Enable Terser DCE through refactoring • Adds side-effect free annotations
  106. @filipematossilv class MyClass {}; MyClass.prop = 42; // terser output

    (class{}).prop=42; const MyClass = /*@ __PURE __ */(() => { class MyClass { } MyClass.prop = 42; return MyClass; })(); // terser output // nothing! input output
  107. @mgechev ngc tsc opt wp terser tsc

  108. twitter.com/mgechev

  109. @yourtwitter @mgechev Differential loading • Produce ES5 bundles for newer

    browsers • Do not send polyfills to modern browsers • Smaller payload • Do not downlevel modern features • Faster execution • Smaller payload
  110. @mgechev -65KB polyfills ~2-10% smaller bundles

  111. @mgechev Step 1: Load HTML Step 2: Look at script

    tags Step 2: Download right version Differential loading
  112. @yourtwitter Differential loading <!DOCTYPE html> <html lang="en"> <head> <title>Differential loading

    </title> </head> <body> <script type="module" src="app-es2015.js"> </script> <script nomodule src="app-es5.js"> </script> </body> </html>
  113. @yourtwitter Differential loading <!DOCTYPE html> <html lang="en"> <head> <title>Differential loading

    </title> </head> <body> <script type="module" src="app-es2015.js"> </script> <script nomodule src="app-es5.js"> </script> </body> </html>
  114. @filipematossilv @mgechev Putting it all together • ngc ◦ Compiles

    templates to efficient instructions • Webpack ◦ only includes used modules in side-effect free libraries ◦ makes large modules from many small modules • Build Optimizer ◦ refactors TS and AOT code for DCE ◦ adds side-effect free annotations for terser • Terser ◦ takes large annotated modules and removes all unused code • TypeScript ◦ downlevels ES2015 to ES5
  115. @mgechev Debugging your build

  116. @yourtwitter $ NG_BUILD_PROFILING=true ng build --prod Profile your build

  117. @mgechev

  118. @mgechev

  119. @mgechev Developer facing APIs

  120. @yourtwitter $ ng build --prod Building a project

  121. @mgechev

  122. @mgechev

  123. @mgechev Thank you! twitter.com/mgechev github.com/mgechev blog.mgechev.com Survey: mgv.io/talk