# Babel插件开发指南

status: Draft tags: 前端工程化 Created time: November 2, 2023 2:08 PM

## 访问者

它通过注册一组访问者函数，对 AST 进行遍历和修改。当 Babel 遍历到某个节点时，它会调用一个名为 `traverse` 的函数，这个函数会接收访问者函数作为参数，并将当前节点传递给访问者函数进行处理。

在插件开发中，我们可以注册一组访问者函数，用于处理 AST 中的不同类型的节点。这些访问者函数按照节点类型进行组织，通常分为两类：

* 进入节点时被调用的函数，通常以 `enter` 为前缀；
* 离开节点时被调用的函数，通常以 `exit` 为前缀。

例如，以下是一个简单的插件，用于将所有标识符替换为它们的大写形式：

```jsx
javascriptCopy Code
export default function() {
  return {
    name: "uppercase-identifiers",
    visitor: {
      Identifier(path) {
        path.node.name = path.node.name.toUpperCase();
      },
    },
  };
}

```

在这个插件中，我们注册了一个名为 `Identifier` 的访问者函数，用于处理 AST 中的标识符节点。当 Babel 遍历到一个标识符节点时，它会调用这个访问者函数，并将当前节点传递给它进行处理。

在访问者函数中，我们可以通过 `path` 参数获取当前节点的信息。`path` 对象是一个封装了当前节点和它的父节点、兄弟节点等信息的对象，它提供了一组方法用于遍历和修改 AST。

## Path

Path 是 Babel 插件系统中的一个重要概念，它代表了 AST 中的一个节点，并提供了一些有用的方法和属性。在访问者函数中，我们可以通过 `path` 参数获取当前节点的 Path 对象，并使用它进行遍历和修改。

以下是 Path 对象常用的一些方法和属性：

* `path.node`：获取当前节点的 AST 节点。
* `path.parent`：获取当前节点的父节点的 Path 对象。
* `path.scope`：获取当前节点所在作用域的 Scope 对象。
* `path.traverse()`：遍历当前节点的子节点，并调用指定的访问者函数进行处理。
* `path.replaceWith()`：用指定的节点替换当前节点。
* `path.insertBefore()`：在当前节点之前插入一个新的节点。
* `path.insertAfter()`：在当前节点之后插入一个新的节点。
* `path.remove()`：删除当前节点。

例如，以下是一个简单的插件，用于将所有变量声明替换为 `var` 声明：

```jsx
javascriptCopy Code
export default function() {
  return {
    name: "var-declaration",
    visitor: {
      VariableDeclaration(path) {
        path.node.kind = "var";
      },
    },
  };
}

```

在这个插件中，我们注册了一个名为 `VariableDeclaration` 的访问者函数，用于处理 AST 中的变量声明节点。当 Babel 遍历到一个变量声明节点时，它会调用这个访问者函数，并将当前节点的 Path 对象传递给它进行处理。

在访问者函数中，我们使用 `path.node` 获取当前节点的 AST 节点，并将其 `kind` 属性设置为 `"var"`，从而将变量声明替换为 `var` 声明。

## AST节点（Abstract Syntax Tree Nodes）

抽象语法树节点是表示代码结构的对象。在Babel中，每个AST节点都有一个类型以及相关属性，用于描述代码的不同部分，如函数、变量、表达式等。Babel插件开发中的大部分工作都涉及到处理或修改AST节点。

每个AST节点都有一个类型（type）属性，它指定了节点所代表的语言结构类型。例如，函数调用节点的类型是“CallExpression”，变量声明节点的类型是“VariableDeclaration”。Babel支持多种类型的AST节点，每种类型的节点都代表不同的代码结构。

在处理AST节点时，您需要了解该节点的类型及其属性，并进行相应的操作。例如，如果您需要修改函数调用节点的参数，则需要访问其arguments属性。如果您需要创建新的AST节点，则需要了解该节点的类型和必需属性。

Babel中的AST节点是由@babel/types模块定义的。该模块提供一组方法，用于创建、更新和查询AST节点。使用@babel/types模块，您可以轻松地创建和操作AST节点。例如，下面是一个使用@babel/types模块创建新函数调用节点的示例：

```jsx
javascriptCopy Code
const t = require('@babel/types');

// 创建一个名为foo的函数调用节点const node = t.CallExpression(
  t.Identifier('foo'),
  [t.StringLiteral('arg1'), t.BooleanLiteral(true)]
);

```

这里，我们使用@babel/types模块提供的`CallExpression()`方法创建一个新的函数调用节点。我们指定了函数名称（Identifier节点），以及两个参数（StringLiteral和BooleanLiteral节点）。

了解和处理AST节点对于插件开发和代码转换至关重要。

## 转换器（Transformer）

转换器是Babel插件中负责实际代码转换的组件。转换器将Visitor与AST节点联系起来，根据Visitor定义的规则对AST进行遍历并进行相应的转换操作。插件的转换器通常由一个或多个Visitors组成。使用Visitors，转换器可以遍历AST节点并对其进行相应的操作。

转换器的主要作用是实现代码转换逻辑。如果您需要在代码中进行特定的更改或优化，则可以编写一个转换器来实现该逻辑。转换器通常根据代码结构定义Visitors，并针对Visitors中定义的节点类型执行相应的操作。

例如，下面是一个简单的转换器，用于将箭头函数转换为普通函数：

```jsx
javascriptCopy Code
const { types: t } = require('@babel/core');

const transformer = {
  ArrowFunctionExpression(path) {
    const { node } = path;

    const params = [];
    const body = t.BlockStatement([t.ReturnStatement(node.body)]);

    if (t.isSequenceExpression(node.body)) {
      const expressions = node.body.expressions.map(exp => {
        return t.ExpressionStatement(exp);
      });
      body.body = [...expressions, body.body[0]];
    }

    if (t.isObjectExpression(node.body)) {
      body.body[0].argument = node.body;
    }

    path.replaceWith(t.FunctionExpression(null, params, body));
  },
};

```

这里，我们定义了一个名为“ArrowFunctionExpression”的Visitors，负责处理箭头函数节点。在Visitors中，我们使用@babel/types模块创建了一个新的函数表达式节点，并将其替换为原始箭头函数节点。

转换器是插件开发中非常重要的概念之一。了解如何编写和使用转换器对于实现代码转换逻辑至关重要。

## 节点创建器（Node Builders）

节点创建器是Babel插件中用于创建新的AST节点的工具。在进行代码转换时，您可能需要创建新的AST节点并将其插入到现有的AST中。节点创建器提供了一种简化创建新节点的方式，使您能够方便地生成新的AST节点。

Babel提供了@babel/types模块中的一组节点创建器，用于创建各种类型的AST节点。这些创建器通过提供一个更高级别的接口来简化节点创建过程。例如，如果您需要创建一个新的变量声明节点，可以使用@babel/types模块中的`variableDeclaration()`方法。该方法采用一组参数，用于指定变量名、变量类型和初始值等信息。

节点创建器还允许您从现有节点复制以创建新节点。例如，您可以使用`cloneNode()`方法从现有节点创建一个新节点，并对其进行修改。使用节点创建器，您可以更快速地创建新的AST节点，并减少错误。

下面是一个创建新的变量声明节点的示例：

```jsx
javascriptCopy Code
const { types: t } = require('@babel/core');

const varDecl = t.variableDeclaration(
  'const',
  [t.variableDeclarator(t.identifier('x'), t.numericLiteral(42))]
);

```

这里，我们使用`variableDeclaration()`方法创建一个新的变量声明节点，并指定变量名称、变量类型和初始值。

节点创建器是插件开发中非常重要的概念之一。了解节点创建器如何工作以及如何使用它们可以简化代码转换过程，提高代码质量和效率。

## Scope（作用域）

作用域是指在代码中变量和函数可访问的范围。在插件开发中，了解和处理作用域非常重要。Babel提供了@babel/traverse模块来管理作用域，并提供了一些工具方法用于查找、创建和更新作用域。

@babel/traverse模块提供了一组工具方法，允许您查找当前节点所在的作用域、创建一个新的作用域、更新作用域链等操作。例如，如果您需要查找变量所在的作用域，则可以使用`scope.getBinding()`方法。该方法接受一个变量名作为参数，并返回与该变量名绑定的作用域信息。

作用域还允许您进行变量声明、引用和更新等操作。例如，如果您需要在当前作用域中创建一个新的变量声明，则可以使用`scope.addDeclaration()`方法。该方法接受一个变量名作为参数，并创建一个新的变量声明。如果变量名已经存在，则会抛出错误。

下面是一个查找并更新变量引用的示例：

```jsx
javascriptCopy Code
const { traverse } = require('@babel/traverse');
const { types: t } = require('@babel/core');

const code = `
  const a = 1;
  function foo() {
    console.log(a);
  }
`;

const ast =// 获取抽象语法树traverse(ast, {
  Identifier(path) {
    const { node } = path;
    const scope = path.scope.getBinding(node.name)?.scope;

    if (scope) {
      const binding = scope.getBinding(node.name);

      if (binding?.kind === 'const' || binding?.kind === 'let') {
        const { referencePaths } = binding;

        referencePaths.forEach(refPath => {
          refPath.replaceWith(t.identifier('newName'));
        });
      }
    }
  },
});

```

这里，我们遍历AST并查找所有标识符节点。对于每个标识符节点，我们首先查找其所在的作用域，然后查找与该节点相关联的绑定信息。如果绑定类型为“const”或“let”，则将其所有引用替换为新的标识符。

## Babel的运行机制：

1. 解析：Babel首先会将源代码解析成抽象语法树(AST)，这是因为Babel需要通过操作AST来对代码进行转换。
2. 转换：Babel使用插件来进行代码转换，每个插件都可以对AST进行修改，例如添加、删除、替换节点等。这些插件可以被串联起来形成一个转换管道，每个插件都可以对代码进行不同程度的转换。
3. 生成：最后，Babel会将修改后的AST重新生成为代码，并且保留源代码的格式。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/qian-duan-gong-cheng-hua/babel-cha-jian-kai-fa-zhi-nan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
