Aujourd'hui, nous publions la deuxième partie d'une traduction de l'extension de syntaxe JavaScript utilisant Babel.

→
Première partie vertigineuse
Comment fonctionne l'analyse
L'analyseur reçoit une liste de jetons du système de tokenisation de code et, en considérant les jetons un par un, construit un AST. Afin de prendre une décision sur la façon d'utiliser les jetons et de comprendre quel jeton peut être attendu ensuite, l'analyseur se réfère à la spécification de la grammaire de la langue.
La spécification de grammaire ressemble à ceci:
... ExponentiationExpression -> UnaryExpression UpdateExpression ** ExponentiationExpression MultiplicativeExpression -> ExponentiationExpression MultiplicativeExpression ("*" or "/" or "%") ExponentiationExpression AdditiveExpression -> MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression ...
Il décrit la priorité d'exécution d'expressions ou d'instructions. Par exemple, une expression
AdditiveExpression
peut représenter l'une des constructions suivantes:
- Expression
MultiplicativeExpression
. - Une expression
AdditiveExpression
, suivie d'un opérateur de jeton +
, suivie d'une expression MultiplicativeExpression
. - Une expression
AdditiveExpression
, suivie d'un jeton « -
», suivie d'une expression MultiplicativeExpression
.
Par conséquent, si nous avons l'expression
1 + 2 * 3
, cela ressemblera à ceci:
(AdditiveExpression "+" 1 (MultiplicativeExpression "*" 2 3))
Mais il n'en sera pas ainsi:
(MultiplicativeExpression "*" (AdditiveExpression "+" 1 2) 3)
Le programme, en utilisant ces règles, est converti en code émis par l'analyseur:
class Parser {
Veuillez noter que voici une version extrêmement simplifiée de ce qui est réellement présent dans Babel. Mais j'espère que ce morceau de code nous permettra d'illustrer l'essence de ce qui se passe.
Comme vous pouvez le voir, l'analyseur est, par nature, récursif. Il passe des conceptions les moins prioritaires aux conceptions les plus prioritaires. Par exemple,
parseAdditiveExpression
appelle
parseMultiplicativeExpression
, et cette construction appelle
parseExponentiationExpression
etc. Ce processus récursif est appelé
analyse de descente récursive .
Fonctions this.eat, this.match, this.next
Vous avez peut-être remarqué que dans les exemples précédents, certaines fonctions auxiliaires ont été utilisées, telles que
this.eat
,
this.match
,
this.next
et d'autres. Ce sont les fonctions internes de l'analyseur Babel. Ces fonctions, cependant, ne sont pas propres à Babel; elles sont généralement présentes dans d'autres analyseurs.
- La fonction
this.match
renvoie une valeur booléenne indiquant si le jeton actuel remplit la condition spécifiée. - La fonction
this.next
avance dans la liste des jetons jusqu'au jeton suivant. - La fonction
this.eat
renvoie la même chose que la fonction this.match
et si this.match
renvoie true
, alors this.eat
effectue, avant de renvoyer true
, un appel à this.next
. - La fonction
this.lookahead
vous permet d'obtenir le prochain jeton sans avancer, ce qui aide à prendre une décision sur le nœud actuel.
Si vous regardez à nouveau le code de l'analyseur que nous avons modifié, vous constaterez que sa lecture est devenue beaucoup plus facile:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser { parseStatementContent() {
Je sais que je n'ai pas expliqué en profondeur les caractéristiques des analyseurs. Par conséquent,
ici et
là - quelques ressources utiles sur ce sujet. J'en ai appris beaucoup et je peux vous les recommander.
Vous pourriez être intéressé à savoir comment j'ai pu visualiser la syntaxe que j'ai créée dans Babel AST Explorer lorsque j'ai montré le nouvel attribut «
curry
» qui est apparu dans AST.
Cela est devenu possible grâce au fait que j'ai ajouté une nouvelle fonctionnalité dans Babel AST Explorer qui vous permet de charger votre propre analyseur dans cet outil de recherche AST.
Si vous suivez le chemin
packages/babel-parser/lib
, vous pouvez trouver une version compilée de l'analyseur et une carte de code. Dans le panneau
Babel AST Explorer
, vous pouvez voir le bouton permettant de charger votre propre analyseur. En téléchargeant les
packages/babel-parser/lib/index.js
vous pouvez visualiser l'AST généré à l'aide de votre propre analyseur.
Visualisation ASTNotre plugin pour Babel
Maintenant que l'analyseur est terminé, écrivons un plugin pour Babel.
Mais, vous avez peut-être maintenant des doutes sur la façon dont nous allons utiliser notre propre analyseur Babel, en particulier compte tenu de la pile technologique que nous utilisons pour construire le projet.
Certes, il n'y a rien à craindre. Le plugin Babel peut fournir des capacités d'analyseur. La
documentation connexe est disponible sur le site Web de Babel.
babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() { return { parserOverride(code, opts) { return customParser.parse(code, opts); }, }; }
Depuis que nous avons créé un fork de l'analyseur Babel, cela signifie que toutes les fonctionnalités existantes de l'analyseur, ainsi que les plug-ins intégrés, continueront de fonctionner parfaitement.
Après nous être débarrassés de ces doutes, regardons comment créer une fonction telle qu'elle supporte le curry.
Si vous ne pouviez pas supporter les attentes et avez déjà essayé d'ajouter notre plug-in à votre système de construction de projet, vous remarquerez peut-être que les fonctions qui prennent en charge le curry sont compilées en fonctions régulières.
Cela se produit car, après avoir analysé et transformé le code, Babel utilise
@babel/generator
pour générer du code à partir de l'AST transformé. Puisque
@babel/generator
ne sait rien du nouvel attribut
curry
, il l'ignore simplement.
Si un jour les fonctions qui prennent en charge le curry entrent dans la norme JavaScript, alors vous voudrez peut-être faire un PR pour ajouter un nouveau code
ici .
Afin que la fonction prenne en charge le curry, vous pouvez l'encapsuler dans un
currying
fonction d'ordre supérieur:
function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); }
Si vous êtes intéressé par les caractéristiques de la mise en œuvre du mécanisme de fonctions de curry dans JS - jetez un œil à
ce matériel.
Par conséquent, nous, transformant une fonction qui prend en charge le curry, pouvons le faire:
Pour l'instant, nous ne ferons pas attention au mécanisme de
montée en fonctions en JavaScript, qui vous permet d'appeler la fonction
foo
avant qu'elle ne soit définie.
Voici à quoi ressemble le code de transformation:
babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
Il vous sera beaucoup plus facile de comprendre si vous lisez
ce document sur les transformations dans Babel.
Nous sommes maintenant confrontés à la question de savoir comment donner à ce mécanisme un accès à la fonction de
currying
. Ici, vous pouvez utiliser l'une des deux approches.
▍ Approche n ° 1: on peut supposer que la fonction de curry est déclarée dans le périmètre global
Si oui, alors le travail est déjà fait.
Si, lors de l'exécution du code compilé, il s'avère que la fonction de
currying
n'est pas définie, alors nous rencontrerons un message d'erreur qui ressemble à "le
currying is not defined
". Il est très similaire au message "
régénérateurRuntime n'est pas défini ".
Par conséquent, si quelqu'un utilise votre
babel-plugin-transformation-curry-function
, vous devrez peut-être l'informer qu'il doit installer le polyfill de currying pour s'assurer que ce plugin fonctionne correctement.
▍ Approche n ° 2: vous pouvez utiliser babel / helpers
Vous pouvez ajouter une nouvelle fonction d'assistance à
@babel/helpers
. Il est peu probable que ce développement soit combiné avec le
@babel/helpers
officiel
@babel/helpers
. En conséquence, vous devrez trouver un moyen de montrer à
@babel/core
l'emplacement de votre
@babel/helpers
:
package.json { "resolutions": { "@babel/helpers": "7.6.0--your-custom-forked-version", }
Je n'ai pas essayé cela moi-même, mais je pense que ce mécanisme fonctionnera. Si vous l'essayez et rencontrez des problèmes, j'en
discuterai avec plaisir.
@babel/helpers
nouvelle fonction d'aide à
@babel/helpers
très simple.
Tout d'abord, accédez au
fichier packages / babel-helpers / src / helpers.js et ajoutez une nouvelle entrée:
helpers.currying = helper("7.6.0")` export default function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); } `;
Lors de la description d'une fonction auxiliaire, la version requise
@babel/core
indiquée. Certaines difficultés ici peuvent être causées par le
export default
la fonction de
currying
.
Pour utiliser une fonction d'assistance, il suffit d'appeler
this.addHelper()
:
La commande
this.addHelper
, si nécessaire, incorporera la fonction d'assistance en haut du fichier et renverra un
Identifier
indiquant la fonction implémentée.
Remarques
Je travaille depuis longtemps sur Babel, mais je n'ai pas encore eu à ajouter de fonctionnalités pour prendre en charge la nouvelle syntaxe JavaScript de l'analyseur. J'ai principalement travaillé sur la correction de bugs et l'amélioration de ce qui est pertinent pour les fonctionnalités des langues officielles.
Cependant, depuis un certain temps maintenant, j'étais occupé à l'idée d'ajouter de nouvelles constructions de syntaxe au langage. En conséquence, j'ai décidé d'écrire du matériel à ce sujet et de l'essayer. C'est incroyablement agréable de voir que tout fonctionne exactement comme prévu.
La capacité de contrôler la syntaxe du langage que vous utilisez est une puissante source d'inspiration. Cela permet, en implémentant certaines constructions complexes, d'écrire moins de code, ou d'écrire du code plus simple qu'auparavant. Les mécanismes de transformation de code simple en constructions complexes sont automatisés et transférés à l'étape de compilation. Cela rappelle comment
async/await
résout les problèmes des rappels infernaux et des longues chaînes de promesses.
Résumé
Ici, nous avons parlé de la façon de modifier les capacités de l'analyseur Babel, nous avons écrit notre propre plugin de transformation de code, parlé brièvement de
@babel/generator
et de la création de fonctions d'assistance à l'aide de
@babel/helpers
. Les informations concernant la transformation du code ne sont données que schématiquement. En savoir plus à leur sujet
ici .
Au cours du processus, nous avons abordé certaines caractéristiques des analyseurs. Si ce sujet vous intéresse - alors,
ici et
là - des ressources qui vous sont utiles.
La séquence d'actions que nous avons effectuée est très similaire à une partie du processus qui est effectué lorsqu'une nouvelle fonctionnalité JavaScript est soumise au TC39.
Voici la page du référentiel TC39 où vous pouvez trouver des informations sur les offres en cours. Vous trouverez ici des informations plus détaillées sur la façon de travailler avec des offres similaires. Lors de la proposition d'une nouvelle fonctionnalité JavaScript, celui qui la propose écrit généralement des polyfills ou, en forçant Babel, prépare une démonstration prouvant que la phrase fonctionne. Comme vous pouvez le voir, créer un fork d'un analyseur ou écrire un polyfill n'est pas la partie la plus difficile du processus de proposition de nouvelles fonctionnalités JS. Il est difficile de déterminer le domaine de l'innovation, de planifier et de réfléchir aux options de son utilisation et aux cas limites; Il est difficile de recueillir les opinions et suggestions des membres de la communauté des programmeurs JavaScript. Par conséquent, je voudrais exprimer ma gratitude à tous ceux qui trouvent la force d'offrir de nouvelles fonctionnalités JavaScript TC39, développant ainsi ce langage.
Voici une page sur GitHub qui vous permettra de voir la grande image de ce que nous avons fait ici.
Chers lecteurs! Avez-vous déjà voulu étendre la syntaxe de JavaScript?
