Aujourd'hui, nous publions la première partie de la traduction du matériel, qui est dédiée à la création de vos propres constructions syntaxiques pour JavaScript à l'aide de Babel.

Revue
Tout d'abord, regardons ce que nous réaliserons lorsque nous arriverons à la fin de ce document:
Nous allons implémenter la syntaxe
@@
qui permet les fonctions de
curry . Cette syntaxe est similaire à celle utilisée pour créer des
fonctions de générateur , mais dans notre cas, au lieu du signe
*
, une séquence de caractères
@@
est placée entre le mot-clé de la
function
et le nom de la
function
. Par conséquent, lors de la déclaration de fonctions, vous pouvez utiliser une construction de la
function @@ name(arg1, arg2)
formulaire
function @@ name(arg1, arg2)
.
Dans l'exemple ci-dessus, lorsque vous travaillez avec la fonction
foo
, vous pouvez utiliser son
application partielle . Appeler la fonction
foo
en lui passant autant de paramètres qui est inférieur au nombre d'arguments dont elle a besoin, renverra une nouvelle fonction qui peut prendre les arguments restants:
foo(1, 2, 3);
J'ai choisi la séquence de caractères
@@
car le symbole
@
ne peut pas être utilisé dans les noms de variables. Cela signifie qu'une construction de la
function@@foo(){}
formulaire
function@@foo(){}
sera également syntaxiquement correcte. De plus, "l'opérateur"
@
est utilisé pour les
fonctions de décorateur , et je voulais utiliser quelque chose de complètement nouveau. En conséquence, j'ai choisi la construction
@@
.
Pour atteindre notre objectif, nous devons effectuer les actions suivantes:
- Créez une fourchette de l'analyseur Babel.
- Créez votre propre plugin Babel pour la transformation de code.
On dirait quelque chose d'impossible?
En fait, il n'y a rien de terrible ici, nous analyserons tout en détail ensemble. J'espère que lorsque vous lirez ceci, vous maîtriserez magistralement les subtilités de Babel.
Création d'une fourchette Babel
Accédez au
référentiel Babel sur GitHub et cliquez sur le bouton
Fork
, qui se trouve dans le coin supérieur gauche de la page.
Création d'une fourchette de Babel ( image en taille réelle )Et en passant, si vous venez de créer le fork du projet open source populaire pour la première fois - félicitations!
Maintenant, clonez la fourche Babel sur votre ordinateur et
préparez-la pour le travail .
$ git clone https:
Permettez-moi maintenant de parler brièvement de l'organisation du référentiel Babel.
Babel utilise un monorepositaire. Tous les packages (par exemple
@babel/core
,
@babel/parser
,
@babel/plugin-transform-react-jsx
et ainsi de suite) se trouvent dans le dossier
packages/
. Cela ressemble Ă ceci:
- doc - packages - babel-core - babel-parser - babel-plugin-transform-react-jsx - ... - Gulpfile.js - Makefile - ...
Je note que Babel utilise un
Makefile pour automatiser les tâches. Lors de la construction d'un projet par la
make build
,
Gulp est utilisé comme gestionnaire de tâches.
Conversion de code en AST Short Course
Si vous n'ĂŞtes pas familier avec des concepts tels que «analyseur» et «arbre de syntaxe abstraite» (AST), puis avant de continuer la lecture, je vous recommande fortement de jeter un Ĺ“il Ă
ce matériel.
Si vous parlez très brièvement de ce qui se passe lors de l'analyse (analyse) du code, vous obtenez les informations suivantes:
- Le code présenté sous forme de chaîne (type
string
) ressemble à une longue liste de caractères: f, u, n, c, t, i, o, n, , @, @, f, ...
- Au tout début, Babel effectue la tokenisation du code. Dans cette étape, Babel scanne le code et crée des jetons. Par exemple, quelque chose comme
function, @@, foo, (, a, ...
- Ensuite, les jetons sont passés à travers l'analyseur pour leur analyse. Ici Babel, basé sur la spécification du langage JavaScript, crée un arbre de syntaxe abstrait.
Voici une excellente ressource pour ceux qui veulent en savoir plus sur les compilateurs.
Si vous pensez que le «compilateur» est quelque chose de très complexe et d'incompréhensible, sachez qu'en réalité tout n'est pas si mystérieux. La compilation consiste simplement à analyser le code et à créer un nouveau code sur sa base, que nous appellerons XXX. Le code XXX peut être représenté par le code machine (peut-être, le code machine est ce qui apparaît d'abord dans l'esprit de la plupart d'entre nous lorsque nous pensons au compilateur). Il peut s'agir d'un code JavaScript compatible avec les anciens navigateurs. En fait, l'une des principales fonctions de Babel est la compilation de code JS moderne en code compréhensible pour les navigateurs obsolètes.
Développer votre propre analyseur pour Babel
Nous allons travailler dans le dossier
packages/babel-parser/
:
- src/ - tokenizer/ - parser/ - plugins/ - jsx/ - typescript/ - flow/ - ... - test/
Nous avons déjà parlé de la tokenisation et de l'analyse. Vous pouvez trouver le code qui implémente ces processus dans des dossiers avec les noms correspondants. Le
plugins/
dossier contient des plugins (plugins) qui étendent les capacités de l'analyseur de base et ajoutent la prise en charge de syntaxes supplémentaires dans le système. C'est exactement ainsi, par exemple, que le
jsx
et le
flow
sont implémentés.
Résolvons notre problème en utilisant la technologie de
développement par le
biais de tests (développement piloté par les tests, TDD). À mon avis, il est plus facile d'écrire un test d'abord, puis, en travaillant progressivement sur le système, de faire ce test sans erreurs. Cette approche est particulièrement bonne lorsque vous travaillez dans une base de code inconnue. TDD permet de comprendre facilement où vous devez apporter des modifications au code pour implémenter vos fonctionnalités prévues.
packages/babel-parser/test/curry-function.js import { parse } from '../lib'; function getParser(code) { return () => parse(code, { sourceType: 'module' }); } describe('curry function syntax', function() { it('should parse', function() { expect(getParser(`function @@ foo() {}`)()).toMatchSnapshot(); }); });
Vous pouvez exécuter le test de
babel-parser
comme ceci:
TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only
. Cela vous permettra de voir les erreurs:
SyntaxError: Unexpected token (1:9) at Parser.raise (packages/babel-parser/src/parser/location.js:39:63) at Parser.raise [as unexpected] (packages/babel-parser/src/parser/util.js:133:16) at Parser.unexpected [as parseIdentifierName] (packages/babel-parser/src/parser/expression.js:2090:18) at Parser.parseIdentifierName [as parseIdentifier] (packages/babel-parser/src/parser/expression.js:2052:23) at Parser.parseIdentifier (packages/babel-parser/src/parser/statement.js:1096:52)
Si vous trouvez que la visualisation de tous les tests prend trop de temps, vous pouvez, pour exécuter le test souhaité, appeler directement
jest
:
BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/curry-function.js
Notre analyseur a découvert 2
@
jetons, apparemment complètement innocents, où ils ne devraient pas être.
Comment le savais-je? La réponse à cette question nous aidera à trouver l'utilisation du mode de surveillance de code lancé par la
make watch
.
La visualisation de la pile d'appels nous conduit Ă
packages / babel-parser / src / parser / expression.js , oĂą l'exception
this.unexpected()
levée.
Ajoutez quelques commandes de journalisation Ă ce fichier:
packages/babel-parser/src/parser/expression.js parseIdentifierName(pos: number, liberal?: boolean): string { if (this.match(tt.name)) {
Comme vous pouvez le voir, les deux jetons sont
@
:
TokenType { label: '@',
Comment ai-je découvert que les constructions
this.state.type
et
this.lookahead().type
me donneront les jetons actuels et suivants?
J'en parlerai dans la section de ce document consacrée aux fonctions
this.eat
,
this.match
et
this.next
.
Avant de continuer, résumons:
- Nous avons écrit un test pour
babel-parser
. - Nous avons exécuté le test en utilisant
make test-only
. - Nous avons utilisé le mode de surveillance du code à l'aide de
make watch
. - Nous avons appris l'état de l'analyseur et
this.state.type
informations sur le type du jeton actuel ( this.state.type
) dans la console.
Et maintenant, nous allons nous assurer que 2 caractères
@
ne sont pas perçus comme des jetons séparés, mais comme un nouveau jeton
@@
, celui que nous avons décidé d'utiliser pour les fonctions de curry.
Nouveau jeton: "@@"
Tout d'abord, regardons où les types de jetons sont déterminés. Il s'agit du fichier
packages / babel-parser / src / tokenizer / types.js .
Vous trouverez ici une liste de jetons. Ajoutez ici la définition du nouveau jeton
atat
:
packages/babel-parser/src/tokenizer/types.js export const types: { [name: string]: TokenType } = {
Cherchons maintenant l'endroit dans le code où, dans le processus de tokenisation, les jetons sont créés. La recherche de la séquence de caractères
tt.at
dans
babel-parser/src/tokenizer
nous conduit au fichier:
packages / babel-parser / src / tokenizer / index.js . Dans
babel-parser
les types de jetons sont importés en tant que
tt
.
Maintenant, si après le symbole
@
actuel vient un autre
@
, créez un nouveau jeton
tt.atat
au lieu du jeton
tt.at
:
packages/babel-parser/src/tokenizer/index.js getTokenFromCode(code: number): void { switch (code) {
Si vous relancez le test, vous remarquerez que les informations sur les jetons actuels et suivants ont changé:
Ça a déjà l'air plutôt bien. Nous allons continuer le travail.
Nouvel analyseur
Avant de poursuivre, regardez comment les fonctions du générateur sont représentées dans AST.
AST pour la fonction générateur ( image en taille réelle )Comme vous pouvez le voir, l'attribut
generator: true
de l'entité
FunctionDeclaration
indique qu'il s'agit d'une
FunctionDeclaration
générateur.
Nous pouvons adopter une approche similaire pour décrire une fonction qui prend en charge le curry. À savoir, nous pouvons ajouter l'attribut
curry: true
Ă
FunctionDeclaration
.
AST pour la fonction de curry ( image en taille réelle )En fait, nous avons maintenant un plan. Traitons de sa mise en œuvre.
Si vous recherchez dans le code le mot
FunctionDeclaration
, vous pouvez accéder à la fonction
parseFunction
, qui est déclarée dans
packages / babel-parser / src / parser / statement.js . Ici vous pouvez trouver la ligne oĂą l'attribut
generator
est défini. Ajoutez une autre ligne au code:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {
Si nous refaisons le test, une agréable surprise nous attendra. Le code est testé avec succès!
PASS packages/babel-parser/test/curry-function.js curry function syntax âś“ should parse (12ms)
C'est tout? Qu'avons-nous fait pour que le test réussisse miraculeusement?
Pour le savoir, parlons du fonctionnement de l'analyse. Au cours de cette conversation, j'espère que vous comprendrez comment la ligne
node.curry = this.eat(tt.atat);
.
Ă€ suivre ...
Chers lecteurs! Utilisez-vous babel?
