Heute veröffentlichen wir den ersten Teil der Übersetzung des Materials, der der Erstellung eigener Syntaxkonstrukte für JavaScript mit Babel gewidmet ist.

Rückblick
Schauen wir uns zunächst an, was wir erreichen werden, wenn wir am Ende dieses Materials angelangt sind:
Wir werden die 
@@ implementieren, die 
Currying- Funktionen ermöglicht. Diese Syntax ähnelt der zum Erstellen von 
Generatorfunktionen verwendeten . In unserem Fall wird jedoch anstelle des Zeichens 
* eine Folge von 
@@ Zeichen zwischen dem 
function und dem 
function @@ . Wenn Sie Funktionen deklarieren, können Sie daher eine Konstruktion der Formularfunktion 
function @@ name(arg1, arg2) .
Im obigen Beispiel können Sie bei der Arbeit mit der Funktion 
foo deren 
Teilanwendung verwenden . Wenn Sie die Funktion 
foo indem Sie so viele Parameter übergeben, die geringer sind als die Anzahl der benötigten Argumente, wird eine neue Funktion zurückgegeben, die die verbleibenden Argumente annehmen kann:
 foo(1, 2, 3);  
Ich habe die Reihenfolge der 
@@ Zeichen gewählt, da das 
@ -Symbol nicht in Variablennamen verwendet werden kann. Dies bedeutet, dass ein Konstrukt der Formularfunktion 
function@@foo(){} auch syntaktisch korrekt ist. Außerdem wird der Operator 
@ für 
Dekorationsfunktionen verwendet , und ich wollte etwas völlig Neues verwenden. Aus diesem Grund habe ich mich für die 
@@ .
Um unser Ziel zu erreichen, müssen wir folgende Aktionen ausführen:
- Erstellen Sie eine Abzweigung des Babel-Parsers.
- Erstellen Sie Ihr eigenes Babel-Plugin für die Code-Transformation.
Sieht nach etwas Unmöglichem aus?
Tatsächlich gibt es hier nichts Schreckliches. Wir werden alles gemeinsam im Detail analysieren. Ich hoffe, wenn Sie dies lesen, werden Sie die Feinheiten von Babel meisterhaft beherrschen.
Eine Gabel erstellen Babel
Gehen Sie zum Babel- 
Repository auf GitHub und klicken Sie auf die Schaltfläche 
Fork , die sich oben links auf der Seite befindet.
Erstellen einer Gabel von Babel ( Bild in voller Größe )Übrigens, wenn Sie gerade zum ersten Mal die Gabelung des beliebten Open Source-Projekts erstellt haben - herzlichen Glückwunsch!
Klonen Sie nun die Babel-Gabel auf Ihren Computer und 
bereiten Sie sie für die Arbeit vor .
 $ git clone https: 
Lassen Sie mich nun kurz auf die Organisation des Babel-Repositorys eingehen.
Babel verwendet ein Monorepository. Alle Pakete (z. B. 
@babel/core , 
@babel/parser , 
@babel/plugin-transform-react-jsx usw.) befinden sich im Ordner 
packages/ . Es sieht so aus:
 - doc - packages  - babel-core  - babel-parser  - babel-plugin-transform-react-jsx  - ... - Gulpfile.js - Makefile - ... 
Ich stelle fest, dass Babel ein 
Makefile verwendet, um Aufgaben zu automatisieren. Beim 
make build eines Projekts mit dem 
make build wird 
Gulp als Task-Manager verwendet.
Code-Konvertierung in einen AST-Kurzkurs
Wenn Sie mit Konzepten wie „Parser“ und „Abstract Syntax Tree“ (AST) nicht vertraut sind, empfehle ich Ihnen dringend, sich 
dieses Material anzusehen, bevor Sie weiterlesen.
Wenn Sie sehr kurz darüber sprechen, was beim Parsen (Parsen) des Codes passiert, erhalten Sie Folgendes:
- Der als Zeichenfolge dargestellte Code ( string) sieht aus wie eine lange Liste von Zeichen:f, u, n, c, t, i, o, n, , @, @, f, ...
- Zu Beginn führt Babel eine Code-Tokenisierung durch. In diesem Schritt scannt Babel den Code und erstellt Token. Zum Beispiel so etwas wie function, @@, foo, (, a, ...
- Dann werden die Token zum Parsen durch den Parser geleitet. Hier erstellt Babel basierend auf der Spezifikation der JavaScript-Sprache einen abstrakten Syntaxbaum.
Hier ist eine großartige Ressource für diejenigen, die mehr über Compiler erfahren möchten.
Wenn Sie denken, dass der „Compiler“ etwas sehr Komplexes und Unverständliches ist, dann wissen Sie, dass in Wirklichkeit nicht alles so mysteriös ist. Beim Kompilieren wird einfach der Code analysiert und auf seiner Grundlage ein neuer Code erstellt, den wir XXX nennen werden. Der XXX-Code kann durch Maschinencode dargestellt werden (vielleicht taucht Maschinencode zuerst in den Köpfen der meisten von uns auf, wenn wir an den Compiler denken). Dies kann JavaScript-Code sein, der mit älteren Browsern kompatibel ist. Tatsächlich ist eine der Hauptfunktionen von Babel die Kompilierung von modernem JS-Code zu Code, der für veraltete Browser verständlich ist.
Entwickeln Sie Ihren eigenen Parser für Babel
Wir werden im Ordner 
packages/babel-parser/ :
 - src/  - tokenizer/  - parser/  - plugins/    - jsx/    - typescript/    - flow/    - ... - test/ 
Wir haben bereits über Tokenisierung und Analyse gesprochen. Sie finden den Code, der diese Prozesse implementiert, in Ordnern mit den entsprechenden Namen. Die 
plugins/ Ordner enthalten Plugins (Plug-Ins), die die Funktionen des Basisparsers erweitern und dem System Unterstützung für zusätzliche Syntaxen hinzufügen. Genau so wird beispielsweise 
flow Unterstützung von 
jsx und 
flow implementiert.
Lösen wir unser Problem mithilfe der 
Entwicklungstechnologie durch Testen (Test Driven Development, TDD). Meiner Meinung nach ist es am einfachsten, zuerst einen Test zu schreiben und dann nach und nach am System diesen Test fehlerfrei auszuführen. Dieser Ansatz eignet sich besonders gut, wenn Sie in einer unbekannten Codebasis arbeiten. Mit TDD können Sie leicht nachvollziehen, wo Sie Änderungen am Code vornehmen müssen, um die beabsichtigte Funktionalität zu implementieren.
 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();  }); }); 
Sie können den Test für 
babel-parser folgendermaßen 
TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only : 
TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only . Auf diese Weise können Sie die Fehler anzeigen:
 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) 
Wenn Sie feststellen, dass das Anzeigen aller Tests zu lange dauert, können Sie, um den gewünschten Test auszuführen, 
jest direkt aufrufen:
 BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/curry-function.js 
Unser Parser entdeckte 2 
@ Token, scheinbar völlig unschuldig, wo sie nicht sein sollten.
Woher wusste ich das? Die Antwort auf diese Frage hilft uns, die Verwendung des Codeüberwachungsmodus zu finden, der mit dem 
make watch gestartet wird.
Das Anzeigen des Aufrufstapels führt uns zu 
packages / babel-parser / src / this.unexpected() / expression.js , wo die Ausnahme 
this.unexpected() ausgelöst wird.
Fügen Sie dieser Datei einige Protokollierungsbefehle hinzu:
 packages/babel-parser/src/parser/expression.js parseIdentifierName(pos: number, liberal?: boolean): string {  if (this.match(tt.name)) {     
Wie Sie sehen können, sind beide Token 
@ :
 TokenType {  label: '@',   
Wie habe ich herausgefunden, dass die Konstruktionen 
this.state.type und 
this.lookahead().type mir die aktuellen und nächsten Token geben?
Ich werde darüber in dem Abschnitt dieses Materials 
this.eat , der den Funktionen 
this.eat , 
this.match und 
this.next .
Bevor wir fortfahren, fassen wir zusammen:
- Wir haben einen Test für babel-parser.
- Wir haben den Test nur mit make test-only.
- Wir haben den Code-Überwachungsmodus mit make watch.
- Wir haben den Status des Parsers this.state.typeund Informationen zum Typ des aktuellen Tokens (this.state.type) in der Konsolethis.state.type.
Und jetzt werden wir sicherstellen, dass 2 
@ -Zeichen nicht als separate Token wahrgenommen werden, sondern als neues 
@ @@ , das wir für Curry-Funktionen verwendet haben.
Neues Token: "@@"
Schauen wir uns zunächst an, wo die Arten von Token bestimmt werden. Dies ist die Datei 
packages / babel-parser / src / tokenizer / types.js .
Hier finden Sie eine Liste der Token. Fügen Sie hier die Definition des neuen 
atat Tokens hinzu:
 packages/babel-parser/src/tokenizer/types.js export const types: { [name: string]: TokenType } = {   
Suchen wir nun nach der Stelle im Code, an der beim Tokenisierungsprozess Token erstellt werden. Wenn Sie die Reihenfolge der 
tt.at Zeichen in 
babel-parser/src/tokenizer tt.at babel-parser/src/tokenizer , gelangen Sie zur folgenden Datei: 
packages / babel-parser / src / tokenizer / index.js . Im 
babel-parser werden Tokentypen als 
tt importiert.
Wenn nun nach dem aktuellen 
@ -Symbol ein weiteres 
@ , erstellen 
tt.atat anstelle des 
tt.at Tokens ein neues Token 
tt.atat :
 packages/babel-parser/src/tokenizer/index.js getTokenFromCode(code: number): void {  switch (code) {     
Wenn Sie den Test erneut ausführen, werden Sie feststellen, dass sich die Informationen zu den aktuellen und nächsten Token geändert haben:
 
Es sieht schon ziemlich gut aus. Wir werden die Arbeit fortsetzen.
Neuer Parser
Bevor Sie fortfahren, schauen Sie sich an, wie Generatorfunktionen in AST dargestellt werden.
AST für Generatorfunktion ( Bild in voller Größe )Wie Sie sehen können, gibt das Attribut 
generator: true der Entität 
FunctionDeclaration an, dass es sich um eine Generatorfunktion handelt.
Wir können einen ähnlichen Ansatz verfolgen, um eine Funktion zu beschreiben, die das Curry unterstützt. Wir können nämlich das Attribut 
curry: true zu 
FunctionDeclaration hinzufügen.
AST für die Curry-Funktion ( Bild in voller Größe )Eigentlich haben wir jetzt einen Plan. Beschäftigen wir uns mit seiner Implementierung.
Wenn Sie im Code nach dem Wort 
FunctionDeclaration suchen, können Sie zur Funktion 
parseFunction , die in 
packages / babel-parser / src / parser / statement.js deklariert ist. Hier finden Sie die Zeile, in der das 
generator ist. Fügen Sie dem Code eine weitere Zeile hinzu:
 packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {   
Wenn wir den Test erneut durchführen, erwartet uns eine angenehme Überraschung. Der Code wurde erfolgreich getestet!
 PASS packages/babel-parser/test/curry-function.js  curry function syntax    ✓ should parse (12ms) 
Und das ist alles? Was haben wir getan, um den Test auf wundersame Weise zu bestehen?
Um dies herauszufinden, lassen Sie uns darüber sprechen, wie das Parsen funktioniert. Ich hoffe, Sie werden im Verlauf dieses Gesprächs verstehen, wie die Zeile 
node.curry = this.eat(tt.atat); .
Fortsetzung folgt…
Liebe Leser! Benutzt du babel
