Babel是JavaScript编译器,可以将未来的JavaScript语法转译为当前JavaScript语法。在Babel中,一个插件就是一个函数,这个函数接收一个babel对象,包含了所有Babel转译JavaScript的函数,如types类型、path路径等。一个Babel插件可以通过改变抽象语法树(AST),修改或删除节点的方式,针对源码进行转换。
Babel解析成AST,然后插件更改AST,最后由Babel输出代码
那么Babel的插件模块需要你暴露⼀个function,function内返回visitor
module.export = function(babel){
return {
visitor:{
}
}
}
visitor是对各类型的AST节点做处理的地⽅,那么我们怎么知道Babel⽣成了的AST有哪些节点呢?
很简单,你可以把Babel转换的结果打印出来,或者这⾥有传送⻔: AST explorer
这⾥我们看到 const result = 1 + 2 中的 1 + 1 是⼀个 BinaryExpression 节点,那么在visitor中,我们就处理这个节点
var babel = require('babel-core');
var t = require('babel-types');
const visitor = {
BinaryExpression(path) {
const node = path.node;
let result;
// 判断表达式两边,是否都是数字
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
// 根据不同的操作符作运算
switch (node.operator) {
case "+":
result = node.left.value + node.right.value;
break
case "-":
result = node.left.value - node.right.value;
break;
case "*":
result = node.left.value * node.right.value;
break;
case "/":
result = node.left.value / node.right.value;
break;
case "**":
let i = node.right.value;
while (--i) {
result = result || node.left.value;
result = result * node.left.value;
}
break;
default:
}
}
// 如果上⾯的运算有结果的话
if (result !== undefined) {
// 把表达式节点替换成number字⾯量
path.replaceWith(t.numericLiteral(result));
}
}
};
module.exports = function (babel) {
return {
visitor
};
}
插件写好了,我们运⾏下插件试试
const babel = require("babel-core");
const result = babel.transform("const result = 1 + 2;",{
plugins:[
require("./index")
]
});
console.log(result.code); // const result = 3;
与预期⼀致,那么转换 const result = 1 + 2 + 3 + 4 + 5; 呢?
结果是: const result = 3 + 3 + 4 + 5;
这就奇怪了,为什么只计算了 1 + 2 之后,就没有继续往下运算了?
我们看⼀下这个表达式的AST树
你会发现Babel解析成表达式⾥⾯再嵌套表达式。
表达式( 表达式( 表达式( 表达式(1 + 2) + 3) + 4) + 5)
⽽我们的判断条件并不符合所有的,只符合 1 + 2
// 判断表达式两边,是否都是数字
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}
那么我们得改⼀改
第⼀次计算 1 + 2 之后,我们会得到这样的表达式
表达式( 表达式( 表达式(3 + 3) + 4) + 5)
其中 3 + 3 ⼜符合了我们的条件, 我们通过向上递归的⽅式遍历⽗级节点
⼜转换成这样:
表达式( 表达式(6 + 4) + 5)
表达式(10 + 5)
15
// 如果上⾯的运算有结果的话
if (result !== undefined) {
// 把表达式节点替换成number字⾯量
path.replaceWith(t.numericLiteral(result));
let parentPath = path.parentPath;
// 向上遍历⽗级节点
parentPath && visitor.BinaryExpression.call(this, parentPath);
}
到这⾥,我们就得出了结果 const result = 15;
那么其他运算呢:
const result = 100 + 10 - 50 >>> const result = 60;
const result = (100 / 2) + 50 >>> const result = 100;
const result = (((100 / 2) + 50 * 2) / 50) ** 2 >>> const result = 9;
下面是一个babel插件的实现示例,我们需要将源代码中所有箭头函数表达式转换为普通的函数表达式:
// 将所有箭头函数转换为普通函数
const arrowToNormal = babel => {
const { types: t } = babel;
return {
visitor: {
ArrowFunctionExpression(path) {
// 获取函数参数
const params = path.node.params;
// 获取函数体
const body = path.node.body;
// 创建函数节点
const func = t.functionExpression(null, params, body);
// 将原有节点替换为新建节点
path.replaceWith(func);
},
},
};
};
module.exports = arrowToNormal;
在这个示例中,插件的名称为 arrowToNormal,它接收 babel 对象并返回一个对象。对象 visitor 是一个包含了多个键值对的对象,其键名是针对不同类型节点类型的方法。因为我们需要转换所有箭头函数,所以这里使用 ArrowFunctionExpression 方法。
ArrowFunctionExpression 方法会在抽象语法树(AST)中每次找到箭头函数的节点时被调用,接着提取箭头函数的参数和函数体,再将其创建为函数表达式节点。
最后,我们需要在项目的Babel配置文件中添加插件配置:
module.exports = {
plugins: [
//使用自定义的plugin
"babel-plugin-arrow-to-normal",
],
};
这是一个简单的Babel插件示例,表达了一些基本的 Babel 插件结构,可以根据需要进一步细化实现更复杂的转换功能。