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

Hacking the Angular Compiler

Hacking the Angular Compiler

In this talk we take a deep dive into how Angular transforms components into DOM elements and JavaScript code that is executed inside the browser. We will see how we hooked into this process to teach Angular our own template syntax that makes our code more declarative, concise, and aligned with other APIs in Angular.

You will walk away with a deep understanding of the Angular Compiler and learn an (experimental) way to hack your own syntax into the system!

Links:
https://github.com/d3lm/ng-emojis
https://github.com/typebytes/ngx-template-streams

Big thanks goes to:
Alan Agius, Filipe Silva, and Alex Rickabaugh

7764379521726735c164889159c8f387?s=128

Dominic Elm

October 03, 2019
Tweet

Transcript

  1. Hacking the Angular Compiler with our own Syntax! by Dominic

    Elm and Kwinten Pisman
  2. Do you still understand the code you wrote 6 months

    ago ? @elmd_ @KwintenP
  3. …or the code your co-workers wrote 6 months ago ?

    @elmd_ @KwintenP
  4. … or someone else’s code ? @elmd_ @KwintenP

  5. @elmd_ @KwintenP

  6. Code should express intent! @elmd_ @KwintenP

  7. @elmd_ @KwintenP

  8. @elmd_ @KwintenP

  9. What did you do last night? @elmd_ @KwintenP

  10. What did you do last night? @elmd_ @KwintenP

  11. - . / ❤ @elmd_ @KwintenP

  12. @elmd_ @KwintenP <div *ngIf="data">Data found!#</div> <todo-item *ngFor="let item of items">{{

    item }}#</todo-item> <input type="text" [(ngModel)]="myModel" #/> <div [ngStyles]="myStyles">Custom Styles#</div>
  13. @elmd_ @KwintenP <div ="data">Data found!#</div> <todo-item ="let item of items">{{

    item }}#</todo-item> <input type="text" []="myModel" #/> <div []="myStyles">Custom Styles#</div>
  14. @elmd_ @KwintenP <div ="data">Data found!#</div> <todo-item ="let item of items">{{

    item }}#</todo-item> <input type="text" []="myModel" #/> <div []="myStyles">Custom Styles#</div>
  15. @elmd_ Dominic Elm @KwintenP Kwinten Pisman thoughtram https://thoughtram.io StackBlitz https://stackblitz.com

    strongbrew https://strongbrew.io StackBlitz https://stackblitz.com Google Developer Expert https://bit.ly/2nAqRrZ Google Developer Expert https://bit.ly/2nAqRrZ
  16. Angular Checklist @elmd_ @KwintenP

  17. Angular Profiler DevTools @elmd_ @KwintenP

  18. @elmd_ @KwintenP

  19. js js JS @elmd_ @KwintenP ???

  20. js js JS @elmd_ @KwintenP Compiler

  21. A compiler is a computer program that translates computer code

    written in one programming language into another language. @elmd_ @KwintenP ”
  22. None
  23. Declarative vs Imperative @elmd_ @KwintenP

  24. Tasks of the Angular Compiler @elmd_ @KwintenP

  25. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP
  26. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP Find errors in the structure of our application
  27. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP Find errors in the structure of our application Transform decorators into static properties
  28. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP Find errors in the structure of our application Transform decorators into static properties Transpile TypeScript into JavaScript
  29. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP Find errors in the structure of our application Transform decorators into static properties Transpile TypeScript into JavaScript Generate template instructions
  30. How do we build an Angular application? @elmd_ @KwintenP

  31. @elmd_ @KwintenP Angular CLI

  32. @elmd_ @KwintenP Webpack

  33. @elmd_ @KwintenP Webpack *.bundle.js *.bundle.js main.bundle.js App

  34. @elmd_ @KwintenP Webpack *.bundle.js *.bundle.js main.bundle.js main.ts AppModule AppComponent

  35. @elmd_ @KwintenP webpack.config.js Webpack

  36. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin AngularCompilerPlugin
  37. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code AngularCompilerPlugin
  38. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> AngularCompilerPlugin
  39. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin
  40. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> Resolve Dependencies, Bundle Bundle AngularCompilerPlugin
  41. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost
  42. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost ngProgram AOT ts.Program JIT
  43. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Analysis AOT ngProgram AOT ts.Program JIT
  44. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT
  45. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Type Checking Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT
  46. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Type Checking Emit Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT Transformer
  47. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Type Checking Emit Angular Code Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT Transformer
  48. Cool. But what’s next? @elmd_ @KwintenP

  49. @elmd_ @KwintenP Loader .html

  50. @elmd_ @KwintenP Loader .html <div ="condition"> Angular rocks! #</div>

  51. @elmd_ @KwintenP <div *ngIf="condition"> Angular rocks! #</div> Loader .html <div

    ="condition"> Angular rocks! #</div>
  52. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> Resolve Dependencies, Bundle Bundle AngularCompilerPlugin
  53. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> Resolve Dependencies, Bundle Bundle AngularCompilerPlugin HTML Loader Custom Loader
  54. How?!? @elmd_ @KwintenP

  55. None
  56. None
  57. @elmd_ @KwintenP $

  58. @elmd_ @KwintenP $ ng add ngx-build-plus $

  59. @elmd_ @KwintenP $ ng add ngx-build-plus touch extra-webpack-config.js $ $

  60. module.exports = { module: { rules: [ { test: /\.html$/,

    use: [ { loader: path.resolve('path/to/custom/html/loader') }, { loader: 'raw-loader' #// import file as a string } ] } ] } }; @elmd_ @KwintenP
  61. module.exports = { module: { rules: [ { test: /\.html$/,

    use: [ { loader: path.resolve('path/to/custom/html/loader') }, { loader: 'raw-loader' #// import file as a string } ] } ] } }; @elmd_ @KwintenP
  62. @elmd_ @KwintenP $ ng add ngx-build-plus touch extra-webpack-config.js $ $

  63. @elmd_ @KwintenP $ ng add ngx-build-plus touch extra-webpack-config.js $ touch

    ng-emoji-plugin.js $ $
  64. export default { pre() { #// before build }, post()

    { #// after build }, config(config: webpack.Configuration) { #// after webpack config was built by the CLI } }; @elmd_ @KwintenP
  65. export default { pre() { #// before build }, post()

    { #// after build }, config(config: webpack.Configuration) { #// after webpack config was built by the CLI } }; @elmd_ @KwintenP
  66. export default { pre() { #// before build }, post()

    { #// after build }, config(config: webpack.Configuration) { const acp = findAngularCompilerPlugin(config) as AngularCompilerPlugin; if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); @elmd_ @KwintenP
  67. export default { pre() { #// before build }, post()

    { #// after build }, config(config: webpack.Configuration) { const acp = findAngularCompilerPlugin(config) as AngularCompilerPlugin; if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); @elmd_ @KwintenP
  68. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  69. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  70. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  71. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  72. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  73. @elmd_ @KwintenP $ ng serve

  74. https://bit.ly/2n1Wb32 @elmd_ @KwintenP ng-emojis

  75. But wait, inline templates @elmd_ @KwintenP

  76. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Type Checking Emit Angular Code Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT Transformer
  77. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

    Plugin Plugin AngularCompilerPlugin <emits> Generated Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin WebpackCompilerHost ts.CompilerHost Type Checking Emit Angular Code Resolve AOT Analysis AOT ngProgram AOT ts.Program JIT Transformer Transformer Custom TS Transformer
  78. TypeScript Transformers @elmd_ @KwintenP

  79. AST @elmd_ @KwintenP

  80. AST @elmd_ @KwintenP

  81. AST @elmd_ @KwintenP

  82. AST @elmd_ @KwintenP

  83. AST ( )=> @elmd_ @KwintenP

  84. export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile) #=> {

    #// change sourceFile (AST) }; } @elmd_ @KwintenP
  85. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; }
  86. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; } function findInlineTemplate(sourceFile: ts.SourceFile) { return tsquery(sourceFile, INLINE_TEMPLATE_QUERY); }
  87. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; }
  88. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; } function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext) { const visitor: ts.Visitor = (node: ts.Node) #=> { #// ast walker function return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }
  89. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; } function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext) { const visitor: ts.Visitor = (node: ts.Node) #=> { #// ast walker function return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }
  90. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; } function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext) { const visitor: ts.Visitor = (node: ts.Node) #=> { #// ast walker function return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }
  91. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; } function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext) { const visitor: ts.Visitor = (node: ts.Node) #=> { #// ast walker function return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }
  92. @elmd_ @KwintenP function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext)

    { const visitor: ts.Visitor = (node: ts.Node) #=> { #// ast walker function return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }
  93. function updateInlineTemplate( nodes: Array<ts.Node>, sourceFile: ts.SourceFile, context: ts.TransformationContext) { const

    visitor: ts.Visitor = (node: ts.Node) #=> { if (nodes.includes(node)) { const prop = node as ts.PropertyAssignment; const currentTpl = prop.initializer.getFullText(); const inlineTpl = ts.createNoSubstitutionTemplateLiteral( updateDirectives(currentTpl, true) ); return ts.updatePropertyAssignment(prop, prop.name, inlineTpl); } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); @elmd_ @KwintenP
  94. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  95. if (!acp) { throw new Error('AngularCompilerPlugin not found'); } const

    options: AngularCompilerPluginOptions = { ##...acp.options, directTemplateLoading: false #// ##<-- IMPORTANT }; config.plugins = removeCompilerPlugin(config.plugins, acp); const newCompilerPlugin = new AngularCompilerPlugin(options); acp._transformers = [inlineTplTransformer, ##...acp._transformers]; config.plugins.push(newCompilerPlugin); return config; } }; @elmd_ @KwintenP
  96. None
  97. None
  98. Reactive Programming! @elmd_ @KwintenP

  99. <button (click)=“clicks$.next($event)”>Click Me</button> @Component({...}) export class AppComponent implements OnInit {

    clicks$ = new Subject(); ngOnInit() { // we can either manually subscribe // or use the async pipe this.clicks$.subscribe(console.log); } }
  100. <button (*click)=“clicks$”>Click Me</button> @Component({...}) export class AppComponent implements OnInit {

    @ObservableEvent() clicks$: Observable<any>; ngOnInit() { // we can either manually subscribe // or use the async pipe this.clicks$.subscribe(console.log); } }
  101. CAN WE HAZ IT? PLZZZ

  102. None
  103. None
  104. Key Takeaways @elmd_ @KwintenP

  105. Key Takeaways @elmd_ @KwintenP What the Angular Compiler does and

    how it works
  106. Key Takeaways @elmd_ @KwintenP TypeScript Transformers What the Angular Compiler

    does and how it works
  107. Key Takeaways @elmd_ @KwintenP TypeScript Transformers ngx-template-streams What the Angular

    Compiler does and how it works
  108. Key Takeaways @elmd_ @KwintenP TypeScript Transformers ngx-template-streams Experiment yourself! What

    the Angular Compiler does and how it works
  109. BUT most importantly, you can learn so much while at

    the same time having FUN! @elmd_ @KwintenP
  110. Thank You revo.js! @elmd_ @KwintenP