Saltar al contenido principal

@babel/helper-environment-visitor

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

@babel/helper-environment-visitor es un paquete de utilidades que proporciona un visitante del contexto actual de this.

Instalación

npm install @babel/helper-environment-visitor

Uso

Para usar el paquete en tu plugin de Babel, importa las funciones requeridas desde @babel/helper-environment-visitor:

my-babel-plugin.js
import environmentVisitor, {
requeueComputedKeyAndDecorators
} from "@babel/helper-environment-visitor";

environmentVisitor

Visita todos los nodos AST dentro del mismo contexto de this hasta el nodo raíz del recorrido. Ejecutar este visitante por sí solo es una operación nula (no-op) ya que no modifica los nodos AST. Este visitante está diseñado para usarse con traverse.visitors.merge.

collect-await-expression.plugin.js
module.exports = (api) => {
const { types: t, traverse } = api;
return {
name: "collect-await",
visitor: {
Function(path) {
if (path.node.async) {
const awaitExpressions = [];
// Get a list of related await expressions within the async function body
path.traverse(traverse.visitors.merge([
environmentVisitor,
{
AwaitExpression(path) {
awaitExpressions.push(path);
},
ArrowFunctionExpression(path) {
path.skip();
},
}
]))
}
}
}
}
}

requeueComputedKeyAndDecorators

requeueComputedKeyAndDecorators(path: NodePath): void

Reencola la clave computada y los decoradores de un path de miembro de clase para que sean revisitados después de vaciar la cola actual de recorrido. Consulta la sección de ejemplo para más usos.

my-babel-plugin.js
if (path.isMethod()) {
requeueComputedKeyAndDecorators(path)
}

Ejemplo

Reemplazar this de nivel superior

Supongamos que estamos migrando de JavaScript vanilla a ES Modules. Dado que la palabra clave this equivale a undefined en el nivel superior de un ESModule (especificación), queremos reemplazar todos los this de nivel superior por globalThis:

input.js
// replace this expression to `globalThis.foo = "top"`
this.foo = "top";

() => {
// replace
this.foo = "top"
}

Podemos diseñar un plugin de modificación de código, aquí está nuestra primera versión:

Revision 1: replace-top-level-this-plugin.js
module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
}
}
}

La primera versión funciona para los ejemplos hasta ahora. Sin embargo, no captura realmente la idea de nivel superior: Por ejemplo, no deberíamos reemplazar this dentro de funciones no flecha: ej. declaraciones de funciones, métodos de objeto y métodos de clase:

input.js
function Foo() {
// don't replace
this.foo = "inner";
}

class Bar {
method() {
// don't replace
this.foo = "inner";
}
}

Podemos omitir el recorrido si encontramos tales funciones no flecha. Aquí combinamos múltiples tipos AST con | en el selector del visitante.

Revision 2: replace-top-level-this-plugin.js
module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
"FunctionDeclaration|FunctionExpression|ObjectMethod|ClassMethod|ClassPrivateMethod"(path) {
path.skip();
}
}
}
}

"FunctionDeclaration|..." es una cadena muy larga y puede ser difícil de mantener. Podemos acortarla usando el alias FunctionParent:

Revision 3: replace-top-level-this-plugin.js
module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
FunctionParent(path) {
if (!path.isArrowFunctionExpression()) {
path.skip();
}
}
}
}
}

El plugin funciona en general. Sin embargo, no puede manejar un caso límite donde this de nivel superior se usa dentro de elementos computados de clase:

input.js
class Bar {
// replace
[this.foo = "outer"]() {
// don't replace
this.foo = "inner";
}
}

Aquí hay un árbol de sintaxis simplificado de la sección resaltada anteriormente:

{
"type": "ClassMethod", // skipped
"key": { "type": "AssignmentExpression" }, // [this.foo = "outer"]
"body": { "type": "BlockStatement" }, // { this.foo = "inner"; }
"params": [], // should visit too if there are any
"computed": true
}

Si se omite todo el nodo ClassMethod, entonces no podremos visitar this.foo bajo la propiedad key. Sin embargo, debemos visitarlo ya que podría ser cualquier expresión. Para lograr esto, necesitamos decirle a Babel que omita solo el nodo ClassMethod, pero no su clave computada. Aquí es donde requeueComputedKeyAndDecorators resulta útil:

Revision 4: replace-top-level-this-plugin.js
import {
requeueComputedKeyAndDecorators
} from "@babel/helper-environment-visitor";

module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
FunctionParent(path) {
if (!path.isArrowFunctionExpression()) {
path.skip();
}
if (path.isMethod()) {
requeueComputedKeyAndDecorators(path);
}
}
}
}
}

Todavía falta un caso límite: this puede usarse dentro de claves computadas de propiedades de clase:

input.js
class Bar {
// replace
[this.foo = "outer"] =
// don't replace
this.foo
}

Aunque requeueComputedKeyAndDecorators también puede manejar este caso límite, el plugin se ha vuelto bastante complejo en este punto, con una cantidad significativa de tiempo dedicado a manejar el contexto de this. De hecho, el enfoque en lidiar con this ha desviado la atención del requisito real, que es reemplazar this de nivel superior con globalThis.

El environmentVisitor se creó para simplificar el código extrayendo la lógica propensa a errores del manejo de this en una función auxiliar, para que ya no tengas que lidiar con ello directamente.

Revision 5: replace-top-level-this-plugin.js
import environmentVisitor from "@babel/helper-environment-visitor";

module.exports = (api) => {
const { types: t, traverse } = api;
return {
name: "replace-top-level-this",
visitor: traverse.visitors.merge([
{
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
},
environmentVisitor
]);
}
}

Puedes probar la versión final en el AST Explorer.

Como su nombre indica, requeueComputedKeyAndDecorators también admite decoradores ES:

input.js
class Foo {
// replaced to `@globalThis.log`
@(this.log) foo = 1;
}

Dado que la especificación continúa evolucionando, usar environmentVisitor puede ser más fácil que implementar tu propio visitor para el contexto this.

Encontrar todas las llamadas super()

Este es un fragmento de código de @babel/helper-create-class-features-plugin.

src/misc.ts
const findBareSupers = traverse.visitors.merge<NodePath<t.CallExpression>[]>([
{
Super(path) {
const { node, parentPath } = path;
if (parentPath.isCallExpression({ callee: node })) {
this.push(parentPath);
}
},
},
environmentVisitor,
]);