Vai al contenuto principale

@babel/helper-environment-visitor

Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

@babel/helper-environment-visitor è un pacchetto di utilità che fornisce un visitatore per il contesto this corrente.

Installazione

npm install @babel/helper-environment-visitor

Utilizzo

Per utilizzare il pacchetto nel tuo plugin Babel, importa le funzioni richieste da @babel/helper-environment-visitor:

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

environmentVisitor

Visita tutti i nodi AST all'interno dello stesso contesto this fino al nodo radice di attraversamento. L'esecuzione di questo visitatore da sola è un'operazione nulla poiché non modifica i nodi AST. Questo visitatore è progettato per essere utilizzato 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

Rimette in coda la chiave calcolata e i decoratori di un path membro di classe in modo che vengano rivisitati dopo lo svuotamento della coda corrente di attraversamento. Consulta la sezione esempio per ulteriori utilizzi.

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

Esempio

Sostituire this al livello superiore

Supponiamo di star migrando da JavaScript vanilla a ES Modules. Dato che la parola chiave this è equivalente a undefined al livello superiore di un ESModule (specifica), vogliamo sostituire tutti i this al livello superiore con globalThis:

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

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

Possiamo abbozzare un plugin per la modifica del codice, ecco la nostra prima revisione:

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 prima revisione funziona per gli esempi visti finora. Tuttavia, non coglie appieno il concetto di livello superiore: ad esempio, non dovremmo sostituire this all'interno di funzioni non freccia come dichiarazioni di funzione, metodi di oggetto e metodi di classe:

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

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

Possiamo saltare l'attraversamento quando incontriamo tali funzioni non freccia. Qui combiniamo più tipi AST con | nel selettore del visitatore.

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|..." è una stringa molto lunga e difficile da mantenere. Possiamo abbreviarla utilizzando l'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();
}
}
}
}
}

Il plugin funziona in generale, ma non gestisce un caso limite in cui this al livello superiore viene utilizzato all'interno di elementi di classe calcolati:

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

Ecco un albero sintattico semplificato della sezione evidenziata sopra:

{
"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
}

Se l'intero nodo ClassMethod viene saltato, non potremo visitare this.foo sotto la proprietà key. Tuttavia, dobbiamo visitarlo poiché potrebbe essere un'espressione qualsiasi. Per ottenere ciò, dobbiamo dire a Babel di saltare solo il nodo ClassMethod, ma non la sua chiave calcolata. È qui che requeueComputedKeyAndDecorators diventa utile:

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);
}
}
}
}
}

Manca ancora un caso limite: this può essere utilizzato all'interno di chiavi calcolate di proprietà di classe:

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

Sebbene requeueComputedKeyAndDecorators possa gestire anche questo caso limite, il plugin è diventato piuttosto complesso, con un tempo significativo dedicato alla gestione del contesto this. Di fatto, la focalizzazione sulla gestione di this ha distolto dal requisito effettivo: sostituire this al livello superiore con globalThis.

environmentVisitor è stato creato per semplificare il codice estraendo la logica di gestione di this soggetta a errori in una funzione helper, evitando di doverla gestire direttamente.

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
]);
}
}

Puoi provare la revisione finale su AST Explorer.

Come suggerisce il nome, requeueComputedKeyAndDecorators supporta anche i decoratori ES:

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

Dato che le specifiche continuano a evolversi, utilizzare environmentVisitor può essere più semplice che implementare un proprio visitor per il contesto this.

Trovare tutte le chiamate super()

Questo è un frammento di codice da @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,
]);