Babel 转译原理
Babel 的三个阶段:解析、转换、生成
问题
Babel 是如何将 ES6+ 代码转换为 ES5 代码的?解释 AST 解析、转换、生成三个阶段。
解答
Babel 的工作流程分为三个阶段:
源代码 → 解析(Parse) → AST → 转换(Transform) → 新AST → 生成(Generate) → 目标代码
1. 解析(Parse)
将源代码转换为 AST(抽象语法树)。
const parser = require('@babel/parser');
const code = 'const fn = (a, b) => a + b';
// 解析代码生成 AST
const ast = parser.parse(code, {
sourceType: 'module'
});
console.log(JSON.stringify(ast, null, 2));
生成的 AST 结构(简化):
{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"declarations": [{
"type": "VariableDeclarator",
"id": { "type": "Identifier", "name": "fn" },
"init": {
"type": "ArrowFunctionExpression",
"params": [
{ "type": "Identifier", "name": "a" },
{ "type": "Identifier", "name": "b" }
],
"body": {
"type": "BinaryExpression",
"operator": "+",
"left": { "type": "Identifier", "name": "a" },
"right": { "type": "Identifier", "name": "b" }
}
}
}]
}]
}
2. 转换(Transform)
遍历 AST,通过插件对节点进行增删改。
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const code = 'const fn = (a, b) => a + b';
const ast = parser.parse(code);
// 遍历 AST,将箭头函数转换为普通函数
traverse(ast, {
// 访问箭头函数节点
ArrowFunctionExpression(path) {
const { params, body } = path.node;
// 如果函数体不是 BlockStatement,包装成 return 语句
const functionBody = t.isBlockStatement(body)
? body
: t.blockStatement([t.returnStatement(body)]);
// 创建普通函数表达式替换箭头函数
const functionExpression = t.functionExpression(
null, // id
params, // 参数
functionBody // 函数体
);
path.replaceWith(functionExpression);
}
});
3. 生成(Generate)
将转换后的 AST 转回代码字符串。
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');
const code = 'const fn = (a, b) => a + b';
const ast = parser.parse(code);
traverse(ast, {
ArrowFunctionExpression(path) {
const { params, body } = path.node;
const functionBody = t.isBlockStatement(body)
? body
: t.blockStatement([t.returnStatement(body)]);
path.replaceWith(t.functionExpression(null, params, functionBody));
}
});
// 生成代码
const output = generate(ast, {}, code);
console.log(output.code);
// 输出: const fn = function (a, b) { return a + b; };
完整示例:自定义 Babel 插件
// my-plugin.js
module.exports = function() {
return {
visitor: {
// 将 console.log 转换为 console.warn
CallExpression(path) {
const { callee } = path.node;
if (
callee.type === 'MemberExpression' &&
callee.object.name === 'console' &&
callee.property.name === 'log'
) {
callee.property.name = 'warn';
}
}
}
};
};
使用插件:
const babel = require('@babel/core');
const code = 'console.log("hello")';
const result = babel.transformSync(code, {
plugins: ['./my-plugin.js']
});
console.log(result.code);
// 输出: console.warn("hello");
关键点
- 解析阶段:词法分析(tokenize)+ 语法分析,将代码转为 AST
- 转换阶段:通过 visitor 模式遍历 AST,插件在此阶段工作
- 生成阶段:深度优先遍历 AST,拼接代码字符串
- @babel/types:用于创建和判断 AST 节点的工具库
- path 对象:包含节点信息和操作方法(replaceWith、remove、insertBefore 等)
目录