📜 ⬆️ ⬇️

Angular compiler in 200 lines of code

Hey. My name is Roman, and I'm not the inventor of bicycles. I like the Angular framework and the ecosystem around it, and I develop my web applications with it. From my point of view, the main advantage of Angular in the long term is based on the separation of code between HTML and TypeScript, which was described in detail by one of its developers why-angular-renders-components-with.html This advantage also has a downside: the need for compilation and the complexity of the dynamic compilation of components at runtime. And you want to use the already familiar Angular template syntax to give the user of their applications the ability to customize email templates, generate reports and spreadsheets for printing, or set the export format for xml files! To learn how to do this - welcome under the cat!

Task


In general, the use of Angular templates by the user may look like this: we have a certain set of data:

const data = { project: 'MySuperProject', userName: 'Roman', role: 'admin', projectLink: 'https://example.com/my-super-projectproject' } 

It should be possible to customize the text of the letter that will be sent to the user after editing the project. Using the Angular template, it might look like this:

  <body>  !  {{project}}    <a href="{{projectLink}}">3D   </a> <div *ngIf="role == 'admin'">       <a href="{{projectLink}}?mode=edit"></a> </div> </body> 

Ng-template library


This task can be solved using the Angular compiler on the client (or even the server side), but this is very time consuming and will require dragging a lot of megabytes of code to the client. Why is the Angular compiler so big? This is due to the fact that it supports a sea of ​​diverse functionality for the composition of components and modules, and also contains its own HTML parser! So I decided to write a minimal Angular template converter that will use the HTML parser built into the browser. It was possible to do it in only 200 with a few lines of code in a couple of hours. I decided to share the result with the public on github
')
Using the ng-template library is quite simple:

Install the dependency from npm

 npm install --save @quanterion/ng-template 

or through yarn

 yarn add @quanterion/ng-template 

And use as follows:

 import { compileTemplate, htmlToElement } from '@quanterion/ng-template'; async test() { let data = { name: 'Roman' }; let element = htmlToElement(`<div>{{name}}</div>`); await compileTemplate(element, data); alert(element.outerHTML); } 

Supported syntax


  1. {{Expression}} expressions with the ability to access variables and call functions
  2. Ng-template templates
  3. Ng-container containers
  4. Conditions * ngIf + * ngIf as
  5. Loops * ngFor
  6. Styles [style.xxx] = "value" and [style.xxx.px] = "value"
  7. Conditional Classes [class.xxx] = "value"
  8. Observables {{name $}} with automatic subscription to a value (as an async pipe)

For details, see the tests ng-template.spec.ts

Using Eval


To calculate expressions in templates, eval is used with preference and courtesans. The fact is that in the templates of Angular, access to variables is used without the usual JavaScript prefix of this. Therefore, it is required to call eval (), in which all variables from an object with data are in scope. I could not generate such code for eval (), since view code

 const data = { a: 1, b: () => 4 }; const expression = 'a+b()'; eval('a =1; b = ??;' + expression); 

does not allow to transfer functions

The solution was found by creating a function whose parameters have the field names of the object with the data:

 const data = { a: 1, b: () => 4 }; let entries = [] for (let property in data ) { entries.push([property, data[property]]) } const params = entries.map(e => e[0]); const fun = new Function('code', ...params, `return eval(code)`); const args = entries.map(e => e[1]); const expression = 'a+b()'; const result = fun.call(undefined, expression , ...args); 

PS: I hope in the future, when the API of the new Ivy compiler is stabilized, it will be possible to generate a set of operators for Ivy and create full-fledged components in dynamics!

Link to source

Source: https://habr.com/ru/post/448022/


All Articles