@babel/helper-environment-visitor
@babel/helper-environment-visitor
是一个实用程序包,提供了一个当前 this
上下文访问器。
安装
- npm
- Yarn
- pnpm
npm install @babel/helper-environment-visitor
yarn add @babel/helper-environment-visitor
pnpm add @babel/helper-environment-visitor
用法
要在 Babel 插件中使用该包,请从 @babel/helper-environment-visitor
导入所需函数
import environmentVisitor, {
requeueComputedKeyAndDecorators
} from "@babel/helper-environment-visitor";
environmentVisitor
它访问与根遍历节点相同 this
上下文中的所有 AST 节点。单独运行此访问器是无操作的,因为它不会修改 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 模块。现在 this
关键字在 ESModule 的顶层等效于 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,
]);