Compilateur angulaire 200 lignes

Salut Je m'appelle Roman et je ne suis pas l'inventeur des vélos. J'aime le framework Angular et l'écosystème qui l'entoure, et je développe mes applications web avec. De mon point de vue, le principal avantage d'Angular à long terme est basé sur la séparation du code entre HTML et TypeScript, qui a été décrit en détail par l'un de ses développeurs pourquoi-angular-renders-components-with.html Cet avantage a un inconvénient: le besoin de compilation en principe et la complexité de la compilation dynamique des composants lors de l'exécution. Et donc je veux utiliser la syntaxe de modèle angulaire familière pour donner à l'utilisateur leurs applications la possibilité de personnaliser les modèles de lettres, de générer des rapports et des feuilles de calcul pour l'impression, ou de définir le format d'exportation pour les fichiers xml! Pour savoir comment faire, bienvenue chez cat!

Défi


En général, l'utilisation de modèles angulaires par l'utilisateur peut ressembler à ceci: nous avons un certain ensemble de données:

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

Il est nécessaire de donner la possibilité de personnaliser le texte de la lettre, qui sera envoyé à l'utilisateur après l'édition du projet. En utilisant un modèle angulaire, cela pourrait ressembler à ceci:

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

Bibliothèque de modèles Ng


Ce problème peut être résolu en utilisant le compilateur angulaire côté client (ou même côté serveur), mais il prend beaucoup de temps et nécessitera de faire glisser beaucoup de mégaoctets de code vers le client. Pourquoi le compilateur angulaire est-il si gros? Cela est dû au fait qu'il prend en charge une mer de fonctionnalités diverses pour la composition de composants et de modules, et contient également son propre analyseur HTML! J'ai donc décidé d'écrire un convertisseur de modèle angulaire minimal qui utilisera l'analyseur HTML intégré au navigateur. Nous avons réussi à le faire en seulement 200 avec quelques lignes de code en quelques heures. J'ai décidé de partager le résultat avec le public sur GitHub

L'utilisation de la bibliothèque ng-template est assez simple:

Installer la dépendance depuis npm

 npm install --save @quanterion/ng-template 

ou à travers du fil

 yarn add @quanterion/ng-template 

Et utilisez-le comme suit:

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

Syntaxe prise en charge


  1. Expressions {{expression}} avec la possibilité d'accéder aux variables et aux fonctions d'appel
  2. Ng-templates
  3. Ng-container
  4. Conditions * ngIf + * ngIf as
  5. Cycles * ngPour
  6. Styles [style.xxx] = "valeur" et [style.xxx.px] = "valeur"
  7. Classes conditionnelles [class.xxx] = "valeur"
  8. Observables {{name $}} avec abonnement automatique à une valeur (en tant que canal asynchrone)

Voir les tests ng-template.spec.ts pour plus de détails.

Utiliser Eval


Pour évaluer les expressions dans les modèles, eval est utilisé avec préférence et courtisanes. Le fait est que dans les modèles angulaires, l'accès aux variables est utilisé sans le préfixe JavaScript habituel. Par conséquent, vous devez appeler eval (), qui a toutes les variables de l'objet de données dans la portée. Je n'ai pas réussi à générer un tel code pour eval (), car voir le code

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

ne permet pas de passer des fonctions

La solution a été trouvée en créant une fonction dont les paramètres ont les noms de champs de l'objet avec des données:

 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: J'espère qu'à l'avenir, lorsque l'API du nouveau compilateur Ivy se stabilisera, il sera possible de générer un ensemble d'opérateurs pour Ivy et de créer des composants à part entière en dynamique!

Lien vers la source

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


All Articles