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

Internals of the Angular CLI

Internals of the Angular CLI

Minko Gechev

June 24, 2020
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. @mgechev Disclaimer You don’t need to know any of the

    following content to use the Angular CLI
  2. @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
  3. Regex replacements @Component({ selector: '<%= selector %>' }) const data

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

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

    'app-component' }) const data = {selector: 'app-component'}; fileContent.replace(/<%= (\w+) %>/, function(a, m) { return data[m]; });
  6. @mgechev Conditions • Should be a property decorator • The

    class should be a component • * Should be a symbol from Angular
  7. @mgechev tokenize('const product = a * b;') [ { lexeme:

    'const', type: 'keyword' }, { lexeme: 'product', type: 'identifier' }, { lexeme: '=', type: 'operator' }, ... ]
  8. @Component({ selector: 'app-component', template: '...' }) class AppComponent { ...

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

    class should be a component • * Should be a symbol from Angular
  10. 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
  11. 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
  12. 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
  13. 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
  14. @mgechev Conditions • Should be a property decorator • The

    class should be a component • * Should be a symbol from Angular
  15. @yourtwitter // Get the type checker from the program const

    typeChecker = program.getTypeChecker(); // Get the type of the symbol const type = typeChecker.getTypeAtLocation(node);
  16. @mgechev Strict Mode Strict TypeScript type checking strictTemplates in Angular

    Opt-in ES5 support Opt-in CommonJS support Reduced side-effects
  17. { "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
  18. 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; }; }
  19. { "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
  20. @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
  21. @yourtwitter $ ng run app:serve # ng serve $ ng

    run app:build # ng build Creating a project
  22. @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!
  23. @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
  24. @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
  25. @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!
  26. @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)); /******/ })();
  27. @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
  28. @mgechev Ivy @Component({ selector: 'app', template: ' ...' }) class

    AppComponent { ... } app.component.js app.component.d.ts
  29. @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
  30. @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
  31. @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
  32. @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
  33. @mgechev Step 1: Load HTML Step 2: Look at script

    tags Step 2: Download right version Differential loading
  34. @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>
  35. @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>
  36. @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