@babel/helper-environment-visitor
@babel/helper-environment-visitor
是提供目前 this
context 訪客的實用程式套件。
安裝
- 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
context 中的所有 AST 節點,前往根部 traverse 節點。單獨執行這個訪客並無作用,因為它不會修改 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
的計算式金鑰和裝飾器,以便在目前的 traversal 排程清空後重新拜訪它們。請參閱 範例 區段,取得更多用法說明。
if (path.isMethod()) {
requeueComputedKeyAndDecorators(path)
}
範例
取代頂層 this
假設我們正在從傳統 JavaScript 遷移到 ES 模組。現在,this
關鍵字等於 ES 模組頂層的 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";
}
}
如果我們遇到此類非箭頭函式,我們可以略過遍歷。這裡我們在 visitor 選擇器中使用 |
結合多個 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,
]);