Babel 插件系统 #

什么是插件? #

插件是 Babel 转换代码的核心机制。每个插件负责处理特定的语法特性或执行特定的代码转换。Babel 的灵活性很大程度上来自于其强大的插件系统。

text
┌─────────────────────────────────────────────────────────────┐
│                     Babel 插件系统                           │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  源代码 ──> Parser ──> AST ──> Plugins ──> 新 AST ──> 代码  │
│                              │                               │
│                              ▼                               │
│                    ┌─────────────────┐                      │
│                    │    Plugin A     │                      │
│                    │    Plugin B     │                      │
│                    │    Plugin C     │                      │
│                    └─────────────────┘                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

插件分类 #

语法转换插件 #

转换现代 JavaScript 语法:

插件 说明
@babel/plugin-transform-arrow-functions 箭头函数
@babel/plugin-transform-classes
@babel/plugin-transform-template-literals 模板字符串
@babel/plugin-transform-destructuring 解构赋值
@babel/plugin-transform-spread 展开运算符
@babel/plugin-transform-parameters 参数处理
@babel/plugin-transform-block-scoping 块级作用域
@babel/plugin-transform-modules-commonjs ES 模块转 CommonJS

提案插件 #

支持尚未进入标准的特性:

插件 说明
@babel/plugin-proposal-decorators 装饰器
@babel/plugin-proposal-class-properties 类属性
@babel/plugin-proposal-private-methods 私有方法
@babel/plugin-proposal-private-property-in-object 私有属性检查
@babel/plugin-proposal-pipeline-operator 管道操作符
@babel/plugin-proposal-optional-chaining 可选链
@babel/plugin-proposal-nullish-coalescing-operator 空值合并

功能插件 #

提供特定功能:

插件 说明
@babel/plugin-transform-runtime 运行时辅助函数
@babel/plugin-proposal-object-rest-spread 对象展开
@babel/plugin-syntax-dynamic-import 动态导入语法
@babel/plugin-syntax-import-meta import.meta 语法

插件使用 #

安装插件 #

bash
# 安装单个插件
npm install --save-dev @babel/plugin-transform-arrow-functions

# 安装多个插件
npm install --save-dev \
  @babel/plugin-transform-arrow-functions \
  @babel/plugin-transform-classes \
  @babel/plugin-transform-template-literals

配置插件 #

javascript
// babel.config.js
module.exports = {
  plugins: [
    // 字符串形式
    '@babel/plugin-transform-arrow-functions',

    // 数组形式(带选项)
    ['@babel/plugin-proposal-decorators', { legacy: true }],

    // 内联函数
    function myCustomPlugin() {
      return {
        visitor: {}
      };
    }
  ]
};

插件执行顺序 #

text
┌─────────────────────────────────────────────────────────────┐
│                     插件执行顺序                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  配置:                                                       │
│  plugins: ['a', 'b', 'c']                                   │
│                                                              │
│  执行顺序: a -> b -> c(从前往后)                            │
│                                                              │
│  ┌──────────┐                                               │
│  │ Plugin a │  第一个执行                                    │
│  └────┬─────┘                                               │
│       │                                                      │
│       ▼                                                      │
│  ┌──────────┐                                               │
│  │ Plugin b │  第二个执行                                    │
│  └────┬─────┘                                               │
│       │                                                      │
│       ▼                                                      │
│  ┌──────────┐                                               │
│  │ Plugin c │  第三个执行                                    │
│  └──────────┘                                               │
│                                                              │
│  注意:插件在预设之前执行                                     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

常用插件详解 #

@babel/plugin-transform-runtime #

提取辅助函数,减少代码重复:

javascript
// babel.config.js
module.exports = {
  presets: ['@babel/preset-env'],
  plugins: [
    ['@babel/plugin-transform-runtime', {
      corejs: 3,           // 使用 core-js polyfill
      helpers: true,       // 使用辅助函数
      regenerator: true,   // 使用 regenerator
      useESModules: false  // 使用 CommonJS
    }]
  ]
};

@babel/plugin-proposal-decorators #

支持装饰器语法:

javascript
// babel.config.js
module.exports = {
  plugins: [
    ['@babel/plugin-proposal-decorators', { legacy: true }],
    ['@babel/plugin-proposal-class-properties', { loose: true }]
  ]
};

// 使用
@decorator
class MyClass {
  @property
  myProperty = 42;
}

@babel/plugin-proposal-class-properties #

支持类属性语法:

javascript
// babel.config.js
module.exports = {
  plugins: [
    ['@babel/plugin-proposal-class-properties', { loose: true }]
  ]
};

// 使用
class Counter {
  count = 0;
  handleClick = () => {
    this.count++;
  }
}

@babel/plugin-transform-react-jsx #

JSX 转换(通常由 preset-react 处理):

javascript
// babel.config.js
module.exports = {
  plugins: [
    ['@babel/plugin-transform-react-jsx', {
      pragma: 'React.createElement',
      pragmaFrag: 'React.Fragment',
      throwIfNamespace: true
    }]
  ]
};

@babel/plugin-transform-modules-commonjs #

ES 模块转 CommonJS:

javascript
// babel.config.js
module.exports = {
  plugins: [
    ['@babel/plugin-transform-modules-commonjs', {
      strictMode: true,
      allowTopLevelThis: true,
      loose: false
    }]
  ]
};

AST 基础 #

什么是 AST? #

AST(Abstract Syntax Tree,抽象语法树)是源代码的树形表示:

javascript
// 源代码
const x = 1;

// AST 结构(简化)
{
  type: "Program",
  body: [{
    type: "VariableDeclaration",
    kind: "const",
    declarations: [{
      type: "VariableDeclarator",
      id: {
        type: "Identifier",
        name: "x"
      },
      init: {
        type: "NumericLiteral",
        value: 1
      }
    }]
  }]
}

常见 AST 节点类型 #

节点类型 说明 示例
Program 程序根节点 整个文件
Identifier 标识符 变量名、函数名
Literal 字面量 1, "hello", true
ExpressionStatement 表达式语句 x = 1;
VariableDeclaration 变量声明 const x = 1;
VariableDeclarator 变量声明器 x = 1
FunctionDeclaration 函数声明 function foo() {}
ArrowFunctionExpression 箭头函数 () => {}
CallExpression 函数调用 foo()
MemberExpression 成员表达式 obj.prop
BinaryExpression 二元表达式 a + b
ConditionalExpression 条件表达式 a ? b : c

AST 工具 #

javascript
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generate = require('@babel/generator');
const t = require('@babel/types');

// 解析代码
const code = 'const x = 1;';
const ast = parser.parse(code);

// 遍历 AST
traverse(ast, {
  Identifier(path) {
    console.log('Found identifier:', path.node.name);
  }
});

// 生成代码
const output = generate(ast);
console.log(output.code);

开发自定义插件 #

插件基本结构 #

javascript
// my-plugin.js
module.exports = function myPlugin(babel) {
  const { types: t } = babel;

  return {
    name: 'my-plugin',
    visitor: {
      // 访问者方法
    }
  };
};

访问者模式 #

javascript
module.exports = function myPlugin(babel) {
  const { types: t } = babel;

  return {
    name: 'my-plugin',
    visitor: {
      // 访问标识符节点
      Identifier(path) {
        console.log('Identifier:', path.node.name);
      },

      // 访问函数声明
      FunctionDeclaration(path) {
        console.log('Function:', path.node.id.name);
      },

      // 访问箭头函数
      ArrowFunctionExpression(path) {
        console.log('Arrow function found');
      }
    }
  };
};

示例:反转变量名 #

javascript
// reverse-identifiers.js
module.exports = function reverseIdentifiers(babel) {
  const { types: t } = babel;

  return {
    name: 'reverse-identifiers',
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        if (name.length > 1) {
          path.node.name = name.split('').reverse().join('');
        }
      }
    }
  };
};

// 输入
const hello = 'world';

// 输出
const olleh = 'world';

示例:替换 console.log #

javascript
// remove-console.js
module.exports = function removeConsole(babel) {
  const { types: t } = babel;

  return {
    name: 'remove-console',
    visitor: {
      CallExpression(path) {
        const callee = path.node.callee;

        if (
          t.isMemberExpression(callee) &&
          t.isIdentifier(callee.object, { name: 'console' }) &&
          t.isIdentifier(callee.property, { name: 'log' })
        ) {
          path.remove();
        }
      }
    }
  };
};

// 输入
console.log('debug info');
const x = 1;

// 输出
const x = 1;

示例:添加函数日志 #

javascript
// add-logging.js
module.exports = function addLogging(babel) {
  const { types: t } = babel;

  return {
    name: 'add-logging',
    visitor: {
      FunctionDeclaration(path) {
        const name = path.node.id ? path.node.id.name : 'anonymous';
        const body = path.node.body;

        const consoleLog = t.expressionStatement(
          t.callExpression(
            t.memberExpression(
              t.identifier('console'),
              t.identifier('log')
            ),
            [t.stringLiteral(`Entering function: ${name}`)]
          )
        );

        body.body.unshift(consoleLog);
      }
    }
  };
};

// 输入
function greet(name) {
  return `Hello, ${name}!`;
}

// 输出
function greet(name) {
  console.log("Entering function: greet");
  return "Hello, " + name + "!";
}

示例:转换箭头函数 #

javascript
// arrow-to-function.js
module.exports = function arrowToFunction(babel) {
  const { types: t } = babel;

  return {
    name: 'arrow-to-function',
    visitor: {
      ArrowFunctionExpression(path) {
        path.replaceWith(
          t.functionExpression(
            null,
            path.node.params,
            t.blockStatement([
              t.returnStatement(path.node.body)
            ])
          )
        );
      }
    }
  };
};

// 输入
const add = (a, b) => a + b;

// 输出
var add = function (a, b) {
  return a + b;
};

Path 对象 #

Path 常用方法 #

javascript
module.exports = function(babel) {
  const { types: t } = babel;

  return {
    visitor: {
      Identifier(path) {
        // 获取节点
        const node = path.node;

        // 获取父节点
        const parent = path.parent;
        const parentPath = path.parentPath;

        // 获取作用域
        const scope = path.scope;

        // 替换节点
        path.replaceWith(t.identifier('newName'));

        // 替换为多个节点
        path.replaceWithMultiple([
          t.identifier('a'),
          t.identifier('b')
        ]);

        // 插入兄弟节点
        path.insertBefore(t.identifier('before'));
        path.insertAfter(t.identifier('after'));

        // 移除节点
        path.remove();

        // 跳过子节点
        path.skip();

        // 停止遍历
        path.stop();
      }
    }
  };
};

Path 查询 #

javascript
module.exports = function(babel) {
  return {
    visitor: {
      CallExpression(path) {
        // 查找父函数
        const functionParent = path.getFunctionParent();

        // 查找父级特定类型
        const loopParent = path.findParent(parent => {
          return parent.isForStatement() || parent.isWhileStatement();
        });

        // 检查节点类型
        if (path.isCallExpression()) {
          // ...
        }

        // 匹配节点
        if (path.matchesPattern('console.log')) {
          // ...
        }
      }
    }
  };
};

Scope 和 Binding #

作用域操作 #

javascript
module.exports = function(babel) {
  const { types: t } = babel;

  return {
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        const binding = path.scope.getBinding(name);

        if (binding) {
          // 获取绑定信息
          console.log('Binding kind:', binding.kind);
          console.log('Is constant:', binding.constant);

          // 获取引用
          console.log('References:', binding.references);
          console.log('Referenced paths:', binding.referencePaths);
        }
      }
    }
  };
};

创建新变量 #

javascript
module.exports = function(babel) {
  const { types: t } = babel;

  return {
    visitor: {
      FunctionDeclaration(path) {
        const uid = path.scope.generateUidIdentifier('temp');
        path.scope.push({
          id: uid,
          init: t.numericLiteral(0)
        });
      }
    }
  };
};

插件选项 #

javascript
// my-plugin.js
module.exports = function myPlugin(babel, options) {
  const { types: t } = babel;
  const { prefix = 'log_', suffix = '' } = options;

  return {
    name: 'my-plugin',
    visitor: {
      Identifier(path) {
        path.node.name = prefix + path.node.name + suffix;
      }
    }
  };
};

// babel.config.js
module.exports = {
  plugins: [
    ['./my-plugin.js', { prefix: 'my_', suffix: '_v1' }]
  ]
};

插件开发最佳实践 #

1. 使用 Babel Types #

javascript
// 推荐:使用 t 创建节点
const node = t.identifier('x');

// 不推荐:手动创建对象
const node = { type: 'Identifier', name: 'x' };

2. 处理边界情况 #

javascript
module.exports = function(babel) {
  return {
    visitor: {
      ArrowFunctionExpression(path) {
        const body = path.node.body;

        // 处理表达式体
        if (t.isExpression(body)) {
          // ...
        }

        // 处理块语句体
        if (t.isBlockStatement(body)) {
          // ...
        }
      }
    }
  };
};

3. 保持代码可读 #

javascript
module.exports = function(babel) {
  const { types: t } = babel;

  function createLogStatement(message) {
    return t.expressionStatement(
      t.callExpression(
        t.memberExpression(
          t.identifier('console'),
          t.identifier('log')
        ),
        [t.stringLiteral(message)]
      )
    );
  }

  return {
    visitor: {
      FunctionDeclaration(path) {
        const name = path.node.id.name;
        path.node.body.body.unshift(createLogStatement(`Enter: ${name}`));
      }
    }
  };
};

4. 编写测试 #

javascript
// my-plugin.test.js
const pluginTester = require('babel-plugin-tester');
const myPlugin = require('./my-plugin');

pluginTester({
  plugin: myPlugin,
  tests: [
    {
      code: 'const x = 1;',
      output: 'var x = 1;'
    },
    {
      code: 'const x = 1; const y = 2;',
      output: 'var x = 1; var y = 2;'
    }
  ]
});

调试技巧 #

使用 console.log #

javascript
module.exports = function(babel) {
  return {
    visitor: {
      Identifier(path) {
        console.log('Node:', JSON.stringify(path.node, null, 2));
        console.log('Parent:', path.parent.type);
      }
    }
  };
};

使用 AST Explorer #

访问 AST Explorer 可视化查看 AST 结构。

使用 babel-plugin-tester #

javascript
const pluginTester = require('babel-plugin-tester');
const myPlugin = require('./my-plugin');

pluginTester({
  plugin: myPlugin,
  tests: [
    {
      code: 'input code',
      output: 'expected output',
      error: 'expected error message'  // 如果期望抛出错误
    }
  ]
});

下一步 #

现在你已经掌握了 Babel 插件系统,接下来学习 配置详解 深入了解各种配置选项!

最后更新:2026-03-28