@babel/helper-environment-visitor
@babel/helper-environment-visitor
是提供当前 this
上下文访问者的实用程序包。
¥@babel/helper-environment-visitor
is a utility package that provides a current this
context visitor.
安装
¥Installation
- npm
- Yarn
- pnpm
npm install @babel/helper-environment-visitor
yarn add @babel/helper-environment-visitor
pnpm add @babel/helper-environment-visitor
用法
¥Usage
要在你的 Babel 插件中使用该包,请从 @babel/helper-environment-visitor
导入所需的函数:
¥To use the package in your Babel plugin, import the required functions from @babel/helper-environment-visitor
:
import environmentVisitor, {
requeueComputedKeyAndDecorators
} from "@babel/helper-environment-visitor";
environmentVisitor
它访问相同 this
上下文中的所有 AST 节点到根遍历节点。单独运行这个访问者是无操作的,因为它不会修改 AST 节点。此访问者旨在与 traverse.visitors.merge
一起使用。
¥It visits all AST nodes within the same this
context to the root traverse node. Running this visitor alone is no-op as it does not modify AST nodes. This visitor is meant to be used with 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
的计算键和装饰器,以便在当前遍历队列耗尽后重新访问它们。有关更多用法,请参阅 example 部分。
¥Requeue the computed key and decorators of a class member path
so that they will be revisited after current traversal queue is drained. See the example section for more usage.
if (path.isMethod()) {
requeueComputedKeyAndDecorators(path)
}
示例
¥Example
替换顶层 this
¥Replace top level this
假设我们正在从 vanilla JavaScript 迁移到 ES 模块。现在 this
关键字等同于 ESModule (spec) 顶层的 undefined
,我们要将所有顶层 this
替换为 globalThis
:
¥Suppose we are migrating from vanilla JavaScript to ES Modules. Now that the this
keyword is equivalent to undefined
at the top level of an ESModule (spec), we want to replace all top-level this
to globalThis
:
// replace this expression to `globalThis.foo = "top"`
this.foo = "top";
() => {
// replace
this.foo = "top"
}
我们可以起草一个代码模组插件,这是我们的第一次修订:
¥We can draft a code mod plugin, here is our first revision:
module.exports = (api) => {
const { types: t } = api;
return {
name: "replace-top-level-this",
visitor: {
ThisExpression(path) {
path.replaceWith(t.identifier("globalThis"));
}
}
}
}
到目前为止,第一次修订适用于示例。但是,它并没有真正捕捉到顶层的想法:例如,我们不应该在非箭头函数中替换 this
:例如 函数声明、对象方法和类方法:
¥The first revision works for examples so far. However, it does not really capture the idea of top-level: For example, we should not replace this
within a non-arrow function: e.g. function declaration, object methods and class methods:
function Foo() {
// don't replace
this.foo = "inner";
}
class Bar {
method() {
// don't replace
this.foo = "inner";
}
}
如果遇到这种非箭头函数,我们可以跳过遍历。在这里,我们在访问者选择器中将多个 AST 类型与 |
组合在一起。
¥We can skip traversing if we encounter such non-arrow functions. Here we combine multiple AST types with |
in the visitor selector.
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 别名来缩短它:
¥"FunctionDeclaration|..."
is a really long string and can be difficult to maintain. We can
shorten it by using the FunctionParent alias:
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
的边缘情况:
¥The plugin works generally. However, it can not handle an edge case where top-level this
is used within computed class elements:
class Bar {
// replace
[this.foo = "outer"]() {
// don't replace
this.foo = "inner";
}
}
这是上面高亮部分的简化语法树:
¥Here is a simplified syntax tree of the highlighted section above:
{
"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
派上用场的地方:
¥If the entire ClassMethod
node is skipped, then we won't be able to visit the this.foo
under the key
property. However, we must visit it as it could be any expression. To achieve this, we need to tell Babel to skip only the ClassMethod
node, but not its computed key. This is where requeueComputedKeyAndDecorators
comes in handy:
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
可以在类属性的计算键中使用:
¥There is still one missing edge case: this
can be used within computed keys of a class property:
class Bar {
// replace
[this.foo = "outer"] =
// don't replace
this.foo
}
尽管 requeueComputedKeyAndDecorators
也可以处理这种边缘情况,但此时插件变得相当复杂,需要花费大量时间来处理 this
上下文。事实上,把重点放在处理 this
上已经偏离了实际需求,即用 globalThis
替换顶层 this
。
¥Although requeueComputedKeyAndDecorators
can handle this edge case as well, the plugin has become quite complex at this point, with a significant amount of time spent on handling the this
context. In fact, the focus on dealing with this
has detracted from the actual requirement, which is to replace top-level this
with globalThis
.
创建 environmentVisitor
是为了简化代码,将容易出错的 this
处理逻辑提取到辅助函数中,这样你就不必再直接处理它。
¥The environmentVisitor
is created to simplify the code by extracting the error-prone this
-handling logic into a helper function, so that you no longer have to deal with it directly.
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 资源管理器 上试用最终修订版。
¥You can try out the final revision on the AST Explorer.
顾名思义,requeueComputedKeyAndDecorators
也支持 ES 装饰器:
¥As its name implies, requeueComputedKeyAndDecorators
supports ES decorators as well:
class Foo {
// replaced to `@globalThis.log`
@(this.log) foo = 1;
}
由于规范不断发展,使用 environmentVisitor
比实现你自己的 this
上下文访问者更容易。
¥Since the spec continues to evolve, using environmentVisitor
can be easier than implementing your own this
context visitor.
查找所有 super()
调用
¥Find all super()
calls
这是 @babel/helper-create-class-features-plugin
的 代码片段。
¥This is a code snippet from @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,
]);