@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`キーワードは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";
}
}
このような非矢印関数に遭遇した場合、トラバーサルをスキップできます。ここでは、ビジターセレクターで`|`を使用して複数の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`にアクセスできなくなります。ただし、これは任意の式である可能性があるため、アクセスする必要があります。これを実現するには、ClassMethod
ノードのみをスキップする必要があることをBabelに指示する必要がありますが、その計算されたキーはスキップする必要がありません。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);
}
}
}
}
}
まだ1つのエッジケースが欠けています。`this`はクラスプロパティの計算されたキー内で使用される可能性があります。
class Bar {
// replace
[this.foo = "outer"] =
// don't replace
this.foo
}
requeueComputedKeyAndDecorators
はこのエッジケースも処理できますが、プラグインはこの時点でかなり複雑になり、`this`コンテキストの処理にかなりの時間が費やされています。実際、`this`の処理に焦点を当てているため、実際の要件である最上位レベルの`this`を`globalThis`に置き換えるということが脇に追いやられています。
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,
]);