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

Internals of the Angular CLI

Internals of the Angular CLI

Avatar for Minko Gechev

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