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. @yourtwitter
    Internals of the Angular CLI
    Minko Gechev
    twitter.com/mgechev
    github.com/mgechev
    blog.mgechev.com

    View Slide

  2. twitter.com/mgechev

    View Slide

  3. @mgechev

    View Slide

  4. @mgechev
    Framework

    View Slide

  5. @mgechev
    Framework
    CLI Components

    View Slide

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

    View Slide

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

    View Slide

  8. View Slide

  9. @mgechev
    Disclaimer
    You don’t need to know any of the
    following content to use the Angular CLI

    View Slide

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

    View Slide

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

    View Slide

  12. @mgechev
    @angular/cli

    View Slide

  13. @mgechev
    @angular/cli
    Builders Schematics

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. twitter.com/
    mgechev

    View Slide

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

    View Slide

  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

    View Slide

  22. @mgechev

    View Slide

  23. @mgechev

    View Slide

  24. @mgechev

    View Slide

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

    View Slide

  26. @mgechev
    Scaffolding Updates NgAdd
    Code
    transform

    View Slide

  27. @mgechev
    Scaffolding Updates NgAdd
    Code
    transform

    View Slide

  28. @mgechev
    Code transformation
    ● Direct string replacements
    ● Regular expressions
    ● AST transformation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. Regex replacements
    @ViewChild('foo') foo;
    @ViewChild('foo', { static: true }) foo;

    View Slide

  38. @mgechev
    Conditions
    ● Should be a property decorator
    ● The class should be a component
    ● * Should be a symbol from Angular

    View Slide

  39. Regex replacements
    @ViewChild('foo') foo;
    @ViewChild('foo', { static: true }) foo;

    View Slide

  40. @mgechev
    Source code + Grammar

    View Slide

  41. @mgechev
    Source code + Grammar

    View Slide

  42. twitter.com/mgechev

    View Slide

  43. twitter.com/mgechev

    View Slide

  44. twitter.com/mgechev

    View Slide

  45. @mgechev

    View Slide

  46. twitter.com/mgechev

    View Slide

  47. @mgechev
    Introduction to compilers

    View Slide

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

    View Slide

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

    View Slide

  50. @mgechev
    COMPILER
    Input Output

    View Slide

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

    View Slide

  52. @mgechev
    Input Output
    FRONT-END

    View Slide

  53. @mgechev
    FRONT-END
    LEXICAL
    ANALYSIS
    SYNTAX
    ANALYSIS

    Input Output

    View Slide

  54. @mgechev
    const product = a * b;

    View Slide

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

    View Slide

  56. @mgechev
    tokenize('const product = a * b;')
    [
    { lexeme: 'const', type: 'keyword' },
    { lexeme: 'product', type: 'identifier' },
    { lexeme: '=', type: 'operator' },
    ...
    ]

    View Slide

  57. @mgechev
    parse(tokenize('...'))

    View Slide

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

    View Slide

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

    View Slide

  60. @Component({
    selector: 'app-component',
    template: '...'
    })
    class AppComponent { ... }
    CLASS
    DECORATOR
    OBJ LITERAL
    SELECTOR APP-
    COMPONENT TEMPLATE ‘…’
    PROPERTY[0] PROPERTY[1]

    View Slide

  61. @mgechev
    Conditions
    ● Should be a property decorator
    ● The class should be a component
    ● * Should be a symbol from Angular

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  66. @mgechev
    Conditions
    ● Should be a property decorator
    ● The class should be a component
    ● * Should be a symbol from Angular

    View Slide

  67. @mgechev
    Source code + Grammar + Type checker

    View Slide

  68. @mgechev
    Source code + Grammar + Type checker

    View Slide

  69. @yourtwitter
    // Get the type checker from the program
    const typeChecker = program.getTypeChecker();
    // Get the type of the symbol
    const type = typeChecker.getTypeAtLocation(node);

    View Slide

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

    View Slide

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

    View Slide

  72. @mgechev
    Strict Mode
    Strict TypeScript type checking
    strictTemplates in Angular
    Opt-in ES5 support
    Opt-in CommonJS support
    Reduced side-effects

    View Slide

  73. @mgechev

    View Slide

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

    View Slide

  75. @yourtwitter
    Strictness helps in
    Automated updates
    Compile-time optimizations
    Angular Universal
    Efficient prefetching
    Optimized i18n pipeline

    View Slide

  76. @mgechev
    Scaffolding Updates
    NgAdd
    Code
    transform

    View Slide

  77. @mgechev
    Developer facing APIs

    View Slide

  78. @mgechev
    Scaffolding Updates
    NgAdd
    Code
    transform

    View Slide

  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

    View Slide

  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;
    };
    }

    View Slide

  81. @mgechev
    Scaffolding Updates
    NgAdd
    Code
    transform

    View Slide

  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

    View Slide

  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

    View Slide

  84. @mgechev
    Building Angular

    View Slide

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

    View Slide

  86. @mgechev
    Angular CLI builders
    @angular/cli
    $ ng run [app]:[cmd]
    angular.json
    require(builder)
    builder
    execute

    View Slide

  87. @yourtwitter
    $ ng run app:serve # ng serve
    $ ng run app:build # ng build
    Creating a project

    View Slide

  88. @mgechev
    Browser builder

    View Slide

  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!

    View Slide

  90. @mgechev
    ngc tsc opt wp terser tsc
    ngcc i18n
    ng-xi18n

    View Slide

  91. @mgechev
    ngc tsc opt wp terser tsc

    View Slide

  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

    View Slide

  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

    View Slide

  94. @mgechev
    ngc tsc opt wp terser tsc

    View Slide

  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!

    View Slide

  96. @mgechev
    Dead Code Elimination

    View Slide

  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));
    /******/ })();

    View Slide

  98. @mgechev
    Purity

    View Slide

  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

    View Slide

  100. @mgechev
    ngc tsc opt wp terser tsc

    View Slide

  101. @mgechev
    Ivy
    @Component({
    selector: 'app',
    template: ' ...'
    })
    class AppComponent { ... }
    app.component.js
    app.component.d.ts

    View Slide

  102. @Component({
    selector: 'cmp',
    template: `
    `
    })
    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

    View Slide

  103. @mgechev

    View Slide

  104. @mgechev
    ngc tsc opt wp terser tsc

    View Slide

  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

    View Slide

  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

    View Slide

  107. @mgechev
    ngc tsc opt wp terser tsc

    View Slide

  108. twitter.com/mgechev

    View Slide

  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

    View Slide

  110. @mgechev
    -65KB polyfills
    ~2-10% smaller bundles

    View Slide

  111. @mgechev
    Step 1: Load HTML Step 2: Look at script tags
    Step 2: Download right
    version
    Differential loading

    View Slide

  112. @yourtwitter
    Differential loading



    Differential loading






    View Slide

  113. @yourtwitter
    Differential loading



    Differential loading






    View Slide

  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

    View Slide

  115. @mgechev
    Debugging your build

    View Slide

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

    View Slide

  117. @mgechev

    View Slide

  118. @mgechev

    View Slide

  119. @mgechev
    Developer facing APIs

    View Slide

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

    View Slide

  121. @mgechev

    View Slide

  122. @mgechev

    View Slide

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

    View Slide