Hoy publicamos la primera parte de la traducción del material, que se dedica a crear sus propias construcciones de sintaxis para JavaScript utilizando Babel.

Revisar
Primero, echemos un vistazo a lo que lograremos cuando lleguemos al final de este material:
Vamos a implementar la sintaxis
@@
que permite funciones de
currículum . Esta sintaxis es similar a la utilizada para crear
funciones generadoras , pero en nuestro caso, en lugar del signo
*
, se coloca una secuencia de caracteres
@@
entre la palabra clave de la
function
y el nombre de la
function
. Como resultado, al declarar funciones, puede usar una construcción de la
function @@ name(arg1, arg2)
formulario
function @@ name(arg1, arg2)
.
En el ejemplo anterior, cuando trabaje con la función
foo
, puede usar su
aplicación parcial . Llamar a la función
foo
con pasarle tantos parámetros que sea menor que la cantidad de argumentos que necesita, devolverá una nueva función que puede tomar los argumentos restantes:
foo(1, 2, 3);
Elegí la secuencia de caracteres
@@
porque el símbolo
@
no se puede usar en nombres de variables. Esto significa que una construcción de la
function@@foo(){}
forma
function@@foo(){}
también será sintácticamente correcta. Además, el "operador"
@
se usa para las
funciones de decorador , y quería usar algo completamente nuevo. Como resultado, elegí la construcción
@@
.
Para lograr nuestro objetivo, debemos realizar las siguientes acciones:
- Crea un tenedor del analizador Babel.
- Crea tu propio complemento Babel para la transformación del código.
Parece algo imposible?
De hecho, no hay nada terrible aquí, analizaremos todo en detalle juntos. Espero que cuando leas esto, domines magistralmente las complejidades de Babel.
Creando un tenedor Babel
Vaya al
repositorio de Babel en GitHub y haga clic en el botón
Fork
, que se encuentra en la esquina superior izquierda de la página.
Crear un tenedor de Babel ( imagen a tamaño completo )Y, por cierto, si acaba de crear la bifurcación del popular proyecto de código abierto por primera vez, ¡felicidades!
Ahora clone el tenedor Babel en su computadora y
prepárelo para el trabajo .
$ git clone https:
Ahora permítanme hablar brevemente sobre la organización del repositorio de Babel.
Babel usa un monorepository. Todos los paquetes (por ejemplo,
@babel/core
,
@babel/parser
,
@babel/plugin-transform-react-jsx
etc.) se encuentran en la carpeta
packages/
. Se ve así:
- doc - packages - babel-core - babel-parser - babel-plugin-transform-react-jsx - ... - Gulpfile.js - Makefile - ...
Observo que Babel usa un
Makefile para automatizar tareas. Al construir un proyecto mediante el
make build
,
Gulp se usa como administrador de tareas.
Conversión de Código a Curso Corto AST
Si no está familiarizado con conceptos como "analizador sintáctico" y "Árbol de sintaxis abstracta" (AST), antes de continuar leyendo, le recomiendo que eche un vistazo a
este material.
Si habla brevemente sobre lo que sucede al analizar (analizar) el código, obtiene lo siguiente:
- El código presentado como una cadena (tipo
string
) parece una larga lista de caracteres: f, u, n, c, t, i, o, n, , @, @, f, ...
- Al principio, Babel realiza la tokenización de código. En este paso, Babel escanea el código y crea tokens. Por ejemplo, algo así como la
function, @@, foo, (, a, ...
- Luego, las fichas se pasan a través del analizador para su análisis. Aquí Babel, basado en la especificación del lenguaje JavaScript, crea un árbol de sintaxis abstracta.
Aquí hay un gran recurso para aquellos que quieran aprender más sobre compiladores.
Si crees que el "compilador" es algo muy complejo e incomprensible, entonces debes saber que en realidad no todo es tan misterioso. La compilación simplemente analiza el código y crea un nuevo código sobre la base, que llamaremos XXX. El código XXX puede ser representado por el código de la máquina (quizás, el código de la máquina es lo que aparece primero en la mente de la mayoría de nosotros cuando pensamos en el compilador). Este puede ser un código JavaScript compatible con navegadores heredados. En realidad, una de las funciones principales de Babel es la compilación del código JS moderno en un código que sea comprensible para los navegadores obsoletos.
Desarrollando su propio analizador para Babel
Vamos a trabajar en los
packages/babel-parser/
folder:
- src/ - tokenizer/ - parser/ - plugins/ - jsx/ - typescript/ - flow/ - ... - test/
Ya hemos hablado de tokenización y análisis. Puede encontrar el código que implementa estos procesos en carpetas con los nombres correspondientes. Los
plugins/
carpeta contienen complementos (complementos) que amplían las capacidades del analizador base y agregan soporte para sintaxis adicionales en el sistema. Así es exactamente cómo, por ejemplo, se implementa
jsx
y
flow
soporte de
flow
.
Solucionemos nuestro problema utilizando la tecnología de
desarrollo a través de pruebas (desarrollo basado en pruebas, TDD). En mi opinión, es más fácil escribir una prueba primero, y luego, trabajando gradualmente en el sistema, hacer que esta prueba se ejecute sin errores. Este enfoque es especialmente bueno cuando se trabaja en una base de código desconocida. TDD facilita la comprensión de dónde debe realizar cambios en el código para implementar la funcionalidad deseada.
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(); }); });
Puede ejecutar la prueba para
babel-parser
siguiente manera:
TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only
. Esto le permitirá ver los errores:
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 encuentra que ver todas las pruebas lleva demasiado tiempo, puede, para ejecutar la prueba deseada, llamar directamente a
jest
:
BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/curry-function.js
Nuestro analizador descubrió tokens 2
@
, aparentemente completamente inocentes, donde no deberían estar.
¿Cómo lo supe? La respuesta a esta pregunta nos ayudará a encontrar el uso del modo de monitoreo de código lanzado por el
make watch
.
Mirar la pila de llamadas nos lleva a los
paquetes / babel-parser / src / parser / expression.js , donde
this.unexpected()
lanza la excepción
this.unexpected()
.
Agregue un par de comandos de registro a este archivo:
packages/babel-parser/src/parser/expression.js parseIdentifierName(pos: number, liberal?: boolean): string { if (this.match(tt.name)) {
Como puede ver, ambos tokens son
@
:
TokenType { label: '@',
¿Cómo descubrí que las construcciones
this.state.type
y
this.lookahead().type
me darán los tokens actuales y siguientes?
Hablaré sobre esto en la sección de este material dedicada a las funciones
this.eat
,
this.match
y
this.next
.
Antes de continuar, resumamos:
- Escribimos una prueba para
babel-parser
. - Ejecutamos la prueba usando
make test-only
. - Utilizamos el modo de monitoreo de código usando
make watch
. - Aprendimos sobre el estado del analizador y
this.state.type
información sobre el tipo de token actual ( this.state.type
) en la consola.
Y ahora nos aseguraremos de que los caracteres 2
@
no se perciban como tokens separados, sino como un nuevo token
@@
, el que decidimos usar para las funciones de currículum.
Nuevo token: "@@"
Primero, veamos dónde se determinan los tipos de tokens. Este es el
paquete de archivos
/ babel-parser / src / tokenizer / types.js .
Aquí puedes encontrar una lista de tokens. Agregue aquí la definición del nuevo token de
atat
:
packages/babel-parser/src/tokenizer/types.js export const types: { [name: string]: TokenType } = {
Ahora busquemos el lugar en el código donde, en el proceso de tokenización, se crean tokens. La búsqueda de la secuencia de caracteres
tt.at
en
babel-parser/src/tokenizer
nos lleva al archivo:
packages / babel-parser / src / tokenizer / index.js . En
babel-parser
tipos de tokens se importan como
tt
.
Ahora, si después del símbolo
@
actual aparece otro
@
, cree un nuevo token
tt.atat
lugar del token
tt.at
:
packages/babel-parser/src/tokenizer/index.js getTokenFromCode(code: number): void { switch (code) {
Si ejecuta la prueba nuevamente, notará que la información sobre los tokens actuales y siguientes ha cambiado:
Ya se ve bastante bien. Continuaremos el trabajo.
Nuevo analizador
Antes de continuar, observe cómo se representan las funciones del generador en AST.
AST para la función de generador ( imagen a tamaño completo )Como puede ver, el atributo
generator: true
de la entidad
FunctionDeclaration
indica que esta es una
FunctionDeclaration
generadora.
Podemos adoptar un enfoque similar para describir una función que admita curry. A saber, podemos agregar el atributo
curry: true
a
FunctionDeclaration
.
AST para la función de curry ( imagen a tamaño completo )En realidad, ahora tenemos un plan. Tratemos con su implementación.
Si busca en el código la palabra
FunctionDeclaration
, puede ir a la función
parseFunction
, que se declara en
paquetes / babel-parser / src / parser / Statement.js . Aquí puede encontrar la línea donde se establece el atributo
generator
. Agregue otra línea al código:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {
Si volvemos a ejecutar la prueba, nos espera una agradable sorpresa. ¡El código se ha probado con éxito!
PASS packages/babel-parser/test/curry-function.js curry function syntax ✓ should parse (12ms)
¿Eso es todo? ¿Qué hemos hecho para que la prueba pase milagrosamente?
Para averiguarlo, hablemos sobre cómo funciona el análisis. En el curso de esta conversación, espero que comprenda cómo funciona la línea
node.curry = this.eat(tt.atat);
.
Continuará ...
Estimados lectores! ¿Usas babel?
