CodeWalk

Babel编译原理与AST转换流程

作者:我还是少年 · 2026-05-30 12:55

解释Babel的编译原理,包括解析(Parse)、转换(Transform)、生成(Generate)三阶段,以及AST(抽象语法树)在其中的作用。如何编写一个自定义Babel Plugin?

回答

我还是少年

Babel三阶段编译流程

源代码 → [解析] → AST → [转换] → 新AST → [生成] → 目标代码

1. 解析(Parse)

  • @babel/parser(基于acorn)
  • 词法分析:将源码拆分为tokens(标识符、操作符、括号等)
  • 语法分析:根据tokens构建AST(Abstract Syntax Tree)
  • 生成的AST遵循ESTree规范(Babel有扩展)

2. 转换(Transform)

  • @babel/traverse遍历AST
  • Plugin访问AST节点并修改:
    // AST节点示例
    {
      type: 'ArrowFunctionExpression',
      params: [{ type: 'Identifier', name: 'x' }],
      body: { type: 'BinaryExpression', ... }
    }
    
  • Babel按Plugin/Preset顺序应用的遍历模式

3. 生成(Generate)

  • @babel/generator遍历修改后的AST
  • 根据节点类型生成对应代码字符串
  • 保留原始代码格式(缩进、换行等源映射)

自定义Babel Plugin示例

// 移除console.log的plugin
module.exports = function(api, options) {
  const { types: t } = api;
  
  return {
    visitor: {
      CallExpression(path) {
        const callee = path.node.callee;
        // 检查是否为 console.xxx()
        if (
          t.isMemberExpression(callee) &&
          t.isIdentifier(callee.object, { name: 'console' }) &&
          options.removeAll !== false  // 接受配置
        ) {
          // 移除该语句
          if (path.findParent(p => p.isExpressionStatement())) {
            path.remove();
          }
        }
      }
    }
  };
};

Plugin使用:

{
  "plugins": [
    ["./remove-console", { "removeAll": true }]
  ]
}

关键API:

  • @babel/types:创建/判断AST节点(t.isIdentifier/t.identifier)
  • path.replaceWith:替换节点
  • path.insertBefore/After:插入兄弟节点
  • path.remove:移除节点
  • path.traverse:在子树上继续遍历

@babel/preset-env原理:

  • 根据targets(浏览器列表)确定需要的转换
  • 结合core-js按需注入polyfill
  • 避免不必要的转换,优化输出大小