@babel/helper-environment-visitor
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
@babel/helper-environment-visitor 是一个提供当前 this 上下文访问器的工具包。
安装
- npm
- Yarn
- pnpm
- Bun
npm install @babel/helper-environment-visitor
yarn add @babel/helper-environment-visitor
pnpm add @babel/helper-environment-visitor
bun add @babel/helper-environment-visitor
用法
要在 Babel 插件中使用此包,请从 @babel/helper-environment-visitor 导入所需函数:
import environmentVisitor, {
requeueComputedKeyAndDecorators
} from "@babel/helper-environment-visitor";
environmentVisitor
该访问器会遍历根遍历节点内相同 this 上下文中的所有 AST 节点。单独运行此访问器是无操作(no-op),因为它不会修改 AST 节点。此访问器设计用于与 traverse.visitors.merge 配合使用。
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
将类成员 path 的计算键和装饰器重新排队,使其在当前遍历队列清空后被重新访问。更多用法请参见示例部分。
if (path.isMethod()) {
requeueComputedKeyAndDecorators(path)
}
示例
替换顶层的 this
假设我们正在从原生 JavaScript 迁移到 ES 模块。由于在 ES 模块顶层中,this 关键字等同于 undefined(规范),我们需要将所有顶层的 this 替换为 globalThis:
// replace this expression to `globalThis.foo = "top"`
this.foo = "top";
() => {
// replace
this.foo = "top"
}
我们可以编写一个代码改造插件,以下是初始版本:
module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
}
}
}
当前版本在简单情况下有效,但未能准确捕获"顶层"概念:例如,我们不应在非箭头函数(如函数声明、对象方法和类方法)中替换 this:
function Foo() {
// don't replace
this.foo = "inner";
}
class Bar {
method() {
// don't replace
this.foo = "inner";
}
}
遇到这类非箭头函数时,我们可以跳过遍历。这里我们在访问器选择器中使用 | 组合多个 AST 类型:
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|..." 这样的字符串过长且难以维护。我们可以使用 FunctionParent 别名来简化:
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();
}
}
}
}
}
插件基本可用,但无法处理在计算类元素中使用顶层 this 的边缘情况:
class Bar {
// replace
[this.foo = "outer"]() {
// don't replace
this.foo = "inner";
}
}
以下是上述高亮部分的简化语法树:
{
"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
}
如果跳过整个 ClassMethod 节点,我们将无法访问 key 属性下的 this.foo。但由于它可能是任何表达式,我们必须访问它。为此,需要让 Babel 仅跳过 ClassMethod 节点而非其计算键。这正是 requeueComputedKeyAndDecorators 的用途:
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);
}
}
}
}
}
仍有一个遗漏的边缘情况:this 可能在类属性的计算键中使用:
class Bar {
// replace
[this.foo = "outer"] =
// don't replace
this.foo
}
虽然 requeueComputedKeyAndDecorators 也能处理此情况,但此时插件已变得相当复杂,大量时间花费在处理 this 上下文上。实际上,对 this 处理的关注已偏离了核心需求——用 globalThis 替换顶层的 this。
environmentVisitor 的创建正是为了将易错的 this 处理逻辑提取到辅助函数中,从而简化代码,使开发者无需直接处理这些细节。
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
]);
}
}
你可以在 AST Explorer 上尝试最终版本。
顾名思义,requeueComputedKeyAndDecorators 也支持 ES 装饰器:
class Foo {
// replaced to `@globalThis.log`
@(this.log) foo = 1;
}
由于规范仍在持续演进,使用 environmentVisitor 比自己实现针对 this 上下文的访问者更为便捷。
查找所有 super() 调用
这是来自 @babel/helper-create-class-features-plugin 的代码片段。
const findBareSupers = traverse.visitors.merge<NodePath<t.CallExpression>[]>([
{
Super(path) {
const { node, parentPath } = path;
if (parentPath.isCallExpression({ callee: node })) {
this.push(parentPath);
}
},
},
environmentVisitor,
]);