Babel 编译一个 JavaScript 文件,核心流程分三步:
代码字符串
↓
解析 (Parse) — 用 @babel/parser 生成 AST
↓
转换 (Transform) — 用插件遍历/修改 AST
↓
生成 (Generate) — 用 @babel/generator 输出新 JS 代码
AST(Abstract Syntax Tree,抽象语法树)
AST 是 Babel 在处理 JS 时中间构建的数据结构,表示 JS 的语法结构。
例如代码:
let a = 1;
对应的 AST 结构大概是这样:
{
"type": "VariableDeclaration",
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "NumericLiteral",
"value": 1
}
}
]
}
可以在 ASTExplorer 中看到更直观的结构。
模块 | 作用 |
---|---|
@babel/parser |
把代码转成 AST |
@babel/traverse |
遍历并修改 AST |
@babel/types |
判断、创建 AST 节点 |
@babel/generator |
把 AST 重新转成 JS 代码 |
@babel/core |
编译入口,用于调用插件链 |
目标:将 let a = 1
改成 let b = 2
目录结构示例
my-plugin-test/
├── input.js ← 原始待编译 JS
├── plugin.js ← 自定义 Babel 插件
├── transform.js ← 执行 Babel 编译过程
input.js
let a = 1;
plugin.js
module.exports = function ({ types: t }) {
return {
visitor: {
Identifier(path) {
if (path.node.name === "a") {
path.node.name = "b";
}
},
NumericLiteral(path) {
if (path.node.value === 1) {
path.node.value = 2;
}
}
}
};
};
transform.js
const fs = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
const plugin = require("./plugin");
const sourceCode = fs.readFileSync("input.js", "utf-8");
// Step 1: 解析为 AST
const ast = parser.parse(sourceCode, {
sourceType: "module",
});
// Step 2: 应用插件逻辑(遍历 & 修改 AST)
traverse(ast, plugin({ types: require("@babel/types") }).visitor);
// Step 3: 生成新代码
const output = generate(ast, {}, sourceCode);
console.log(output.code);
运行结果:
node transform.js
输出:
let b = 2;
函数 | 用法 | 说明 |
---|---|---|
t.identifier("x") |
创建标识符节点 | 对应变量名 |
t.stringLiteral("abc") |
创建字符串节点 | 用于赋值或参数 |
t.numericLiteral(123) |
数字字面量 | 常用于赋值 |
t.variableDeclaration("let", [...]) |
创建变量声明 | 第一个参数是 let/const/var |
t.expressionStatement(expr) |
表达式语句 | 常用于插入代码 |
t.isIdentifier(path.node) |
判断是否是标识符节点 | 类型判断 |
操作 | 说明 | 示例 |
---|---|---|
path.node |
获取当前节点 | path.node.name === "x" |
path.replaceWith(newNode) |
替换当前节点 | 替换变量名 |
path.remove() |
删除当前节点 | 清除无用代码 |
path.insertBefore(node) |
在节点前插入 | 插入一段代码 |
path.insertAfter(node) |
在节点后插入 |
1)混淆变量还原
从 _0xabc123
这种变量还原成 loginToken
2)解密函数识别 & 重命名
识别 IIFE 内部生成的混淆函数,重命名为 decrypt()
3)删除死代码
移除 if (false) {...}
结构、debugger 调用
4)解构还原/解包还原
把 var { a } = obj;
还原为 var a = obj.a;
5)还原 async/await 编译结构(Generator)
我们想把这种编译后的代码:
var _fn = _asyncToGenerator(
regeneratorRuntime.mark(function* () {
var data;
return regeneratorRuntime.wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return fetchData();
case 2:
data = _context.sent;
_context.next = 5;
return saveData(data);
case 5:
_context.abrupt("return", "done");
}
});
})
);
还原为 :
async function _fn() {
const data = await fetchData();
await saveData(data);
return "done";
}
1)用 AST Explorer 调试节点结构
网址:AST explorer
选择:@babel/parser
→ JavaScript
→ 看每个节点的 type、属性
2)打印当前节点信息
Identifier(path) {
console.log("标识符节点:", path.node.name);
}
3)可视化生成新代码
const { code } = generate(ast);
console.log(code);
Babel 插件就是在遍历 AST 的过程中,对感兴趣的语法结构进行替换、插入或删除操作,最终生成新的 JS 代码。