Babel 是一个非常流行的 JavaScript 编译器,下面我们将从零到一编写一个 babel 箭头函数语法转换插件,掌握 babel 插件设计思路与编写规范,需求很简单就是将箭头函数转换为普通函数。
const test = ()=>{console.log("Hello World!")
}
如上所示,通过 babel 编译后,生成如下代码:
const test = function(){console.log("Hello World!")
}
1. 自定义插件
1.1. 使用types辅助修改
首先定义箭头函数插件:plugin-arrow-functions.js
const { types: t } = require("@babel/core");module.exports = function () {return {visitor: {ArrowFunctionExpression(path) {// 我当前访问到的箭头函数节点const { node } = path;const body = t.isBlockStatement(node.body)? node.body: t.blockStatement([t.returnStatement(node.body)]);const functionExpression = t.functionExpression(null,node.params,body,node.async);console.log("🚀 ~ ArrowFunctionExpression ~ functionExpression:",functionExpression);// // 假设箭头函数表达式直接返回值,需要特殊处理// if (!t.isBlockStatement(node.body)) {// functionExpression.body = t.blockStatement([// t.returnStatement(node.body),// ]);// }path.replaceWith(functionExpression);}}}
}
通过 types 辅助对象,可以判断当前访问 ast 节点的类型以及对其类型进行加工。
然后在 babel.config.js中定义该插件。
module.exports = {plugins: ["./plugins/arrow-function.js"],
};
最后修改 package.json,添加构建脚本命令。
"scripts": {"build": "babel src -d dist"
}
最后执行pnpm build 执行 babel 编译,在 dist 中输出目标资源,经验证符合要求。
1.2. 使用@babel/helper-plugin-utils
这个方案只是在 visitor 时有所差异,我们可以这样处理:
const { declare } = require("@babel/helper-plugin-utils");
module.exports = declare((api, options) => {const noNewArrows = api.assumption("noNewArrows") ?? options.spec;return {name: "transform-arrow-functions",visitor: {ArrowFunctionExpression(path) {if (!path.isArrowFunctionExpression()) return;path.arrowFunctionToExpression({allowInsertArrow: false,noNewArrows,specCompliant: !noNewArrows,})}}}
})
2. Babel编译过程
Babel 最核心的流程我们可以概括为上图所示,parser => traverse => generator
我们以一个问题来切入:
const a = 1;
let b = 2;
var c = "aiguangyuan";
请将以上代码,转为如下所示 ES5 代码:
var a = 1;
var b = 2;
var c = "aiguangyuan";
2.1. parser
借助@babel/parser将源代码转为 ast 结构,即:
const parser = require('@babel/parser');
const ast = parser.parse(code);
2.2. traverse
借助 @babel/traverse将 ast 进行处理生成新 ast,即:
const traverse = require('@babel/traverse');// 访问者对象
const visitor = {// 遍历声明表达式// 可以直接通过节点的类型操作AST节点。VariableDeclaration(path) {// 替换if (path.node.kind === 'var') {path.node.kind = 'let';}},
};
traverse.default(ast, visitor);
或者:
const traverse = require('@babel/traverse');traverse.default(ast, {enter(path) {if (path.isVariableDeclaration({ kind: 'var' })) {path.node.kind = 'let';}},
});
2.3. generator
借助@babel/generator根据目标 ast 生成目标代码,示例:
const generator = require('@babel/generator');generator.default(ast, {}, code).code;
2.4. 完整过程
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generator = require('@babel/generator');const trans = (code) => {const ast = parser.parse(code);// 访问者对象const visitor = {// 遍历声明表达式// 可以直接通过节点的类型操作AST节点。VariableDeclaration(path) {// 替换if (path.node.kind == 'var') {path.node.kind = 'let';}},};traverse.default(ast, visitor);// 生成代码const newCode = generator.default(ast, {}, code).code;return newCode;
};const code = `const a = 1
let b = 2
var c = 3`;
console.log(trans(code));