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

Dominic Elm

October 03, 2019
Tweet

More Decks by Dominic Elm

Other Decks in Programming

Transcript

  1. @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>
  2. @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>
  3. @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>
  4. @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
  5. A compiler is a computer program that translates computer code

    written in one programming language into another language. @elmd_ @KwintenP ”
  6. Tasks of the Angular Compiler Type checking of our templates

    @elmd_ @KwintenP Find errors in the structure of our application
  7. 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
  8. 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
  9. 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
  10. @elmd_ @KwintenP webpack.config.js Webpack Loader Loader Loader ngc <register> <register>

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

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

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

    Plugin Plugin AngularCompilerPlugin <emits> Angular Code <waits for> Resolve Dependencies, Bundle AngularCompilerPlugin
  14. @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
  15. @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
  16. @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
  17. @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
  18. @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
  19. @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
  20. @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
  21. @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
  22. @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
  23. @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
  24. 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
  25. 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
  26. export default { pre() { #// before build }, post()

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

    { #// after build }, config(config: webpack.Configuration) { #// after webpack config was built by the CLI } }; @elmd_ @KwintenP
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. @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
  36. @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
  37. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; }
  38. @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); }
  39. @elmd_ @KwintenP export function transformer(context: ts.TransformationContext) { return (sourceFile: ts.SourceFile)

    #=> { const template = findInlineTemplate(sourceFile); return updateInlineTemplate(template, sourceFile, context); }; }
  40. @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); }
  41. @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); }
  42. @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); }
  43. @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); }
  44. @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); }
  45. 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
  46. 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
  47. 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
  48. <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); } }
  49. <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); } }
  50. BUT most importantly, you can learn so much while at

    the same time having FUN! @elmd_ @KwintenP