Babel 前段时间听闻后感觉神秘,无从下手?非常理解🧐
编写一个示例后,你会发现它只是一个将 ES6 代码转换为 ES5 的 JavaScript 工具,使我们能在不支持 ES6 的环境中运行代码。Babel 的工作是先将代码解析为抽象语法树(AST),然后遍历这棵树,对其节点进行操作,最终生成新的代码
下面我写了两个简单的例子方便快速开始,源码在文末,可以直接运行
示例:将变量 foo
替换为 bar
让我们看一个简单的例子,从原始代码开始,通过 Babel 插件处理后的输出,到转换过程的详细解释
原始代码
文件 test.js
包含以下 JavaScript 代码:
javascript
const foo = 42;
console.log(foo);
通过 Babel 转换后的输出
转换后的代码将所有的 foo
替换为 bar
:
javascript
const bar = 42;
console.log(bar);
转换过程的核心代码
转换代码如下,我们使用了 Babel 的同步转换方法和一个自定义插件 replaceFooWithBar
javascript
const babel = require("@babel/core");
const replaceFooWithBar = require("./replaceFooWithBar.js");
const fs = require("fs");
const path = require("path");
const code = fs.readFileSync(path.resolve(__dirname, "./test.js"), "utf-8");
const output = babel.transformSync(code, {
plugins: [replaceFooWithBar],
});
console.log(output.code);
插件 replaceFooWithBar.js
的代码
这个 Babel 插件简单但有效,它将所有的 foo
标识符替换为 bar
javascript
module.exports = function ({ types: t }) {
return {
visitor: {
Identifier(path) {
if (path.node.name === "foo") {
path.node.name = "bar";
}
},
},
};
};
解析代码
在 Babel 中,visitor
对象包含一组方法,每个方法对应一种或多种节点类型。当 Babel 遍历 AST 时,如果遇到某个节点类型,它会调用对应的访问者方法
babel.transformSync
javascript
const output = babel.transformSync(code, {
plugins: [replaceFooWithBar],
});
-
code
:-
这是一个包含你想要转换的 JavaScript 代码的字符串。例如
javascriptconst code = 'const foo = 1';
-
-
配置对象(第二个参数):
- 这是一个包含 Babel 配置的对象,指定要使用的插件、预设及其他配置选项。在这个例子中,我们只指定了一个插件
replaceFooWithBar
- 这是一个包含 Babel 配置的对象,指定要使用的插件、预设及其他配置选项。在这个例子中,我们只指定了一个插件
visitor
对象
visitor
对象的键是节点类型的名称,值是对应的处理函数。当 AST 中遇到该类型的节点时,Babel 会调用这个处理函数。处理函数会接收一个路径(path
)对象,表示当前节点在 AST 中的位置
-
导出函数:
javascriptmodule.exports = function ({ types: t }) { // 插件返回一个包含 visitor 对象的对象 return { visitor: { // 访问 Identifier 节点的方法 Identifier(path) { if (path.node.name === "foo") { path.node.name = "bar"; } }, }, }; };
-
visitor
对象:visitor
对象定义了一个Identifier
方法- 当 Babel 遇到
Identifier
节点(表示标识符,例如变量名、函数名等)时,会调用这个方法
-
Identifier
方法:javascriptIdentifier(path) { if (path.node.name === "foo") { path.node.name = "bar"; } }
path
对象表示当前节点及其上下文path.node
是当前节点的具体表示- 检查节点的名称是否为
foo
,如果是,则将其改为bar
看完上面的例子会觉得,直接正则表达式匹配替换不是更简单吗?一句话解决:
javascript
const code = 'const foo = 1';
code.replace(/foo/g, 'bar')
如果有疑惑那么我们再写一个例子,将箭头函数转换为普通函数
示例:将箭头函数转换为普通函数
这个操作常在一些老的浏览器(微软都放弃了ie,到底是谁还在用)中,因为箭头函数是ES6的语法,老的浏览器不支持,所以我们需要将箭头函数转换为普通函数
原始代码
文件 test.js
包含以下 JavaScript 代码:
javascript
const add = (a, b) => a + b;
console.log(add(2, 3));
通过 Babel 转换后的输出
转换后的代码将所有的箭头函数转换为普通函数:
javascript
const add = function (a, b) {
return a + b;
};
console.log(add(2, 3));
转换过程的核心代码
转换代码如下,我们使用了 Babel 的同步转换方法和一个自定义插件 transformArrowFunctions
javascript
const babel = require("@babel/core");
const transformArrowFunctions = require("./transformArrowFunctions.js");
const fs = require("fs");
const path = require("path");
const code = fs.readFileSync(path.resolve(__dirname, "./test.js"), "utf-8");
const output = babel.transformSync(code, {
plugins: [transformArrowFunctions],
});
console.log(output.code);
console.log("------------函数执行如下-----------");
const func1 = new Function("parm1", "parm2", output.code);
func1(1, 2);
插件 transformArrowFunctions.js
的代码
这个 Babel 插件简单但有效,它将所有的 foo
标识符替换为 bar
javascript
module.exports = function ({ types: t }) {
return {
visitor: {
ArrowFunctionExpression(path) {
const { params, body, async } = path.node;
// 如果主体不是 BlockStatement,则将其转换为 BlockStatement
const functionBody = t.isBlockStatement(body)
? body
: t.blockStatement([t.returnStatement(body)]);
const functionExpression = t.functionExpression(
null, // 函数名称,箭头函数是匿名的
params, // 函数参数
functionBody, // 函数体
false, // generator
async // 是否为异步函数
);
// 替换箭头函数为普通函数
path.replaceWith(functionExpression);
},
},
};
};
解析代码
ArrowFunctionExpression 对应的是箭头函数节点,遇到箭头函数就会执行这个方法
箭头函数和普通函数的区别有下面明显的两点:
- 匿名函数
- 没有自己的this,箭头函数的this是继承外层的this
那么在转换之后,那个执行的普通函数也模拟箭头函数的特性,保持执行结果一致
看看上面的代码,首先是判断箭头函数的主体是否是一个代码块,如果不是,就将其转换为代码块
这是因为箭头函数支持极致的简洁,如果主体是一个表达式,那么箭头函数会自动返回这个表达式的值,所以我们需要将其转换为代码块(花括号包裹📦),这样就构造好了函数体
然后就是创建一个普通函数,将箭头函数的参数和主体传入,最后将箭头函数替换为普通函数
FunctionExpression的源码 感兴趣的可以看看,反正我当前是没看明白
总结
babel.transformSync
是 Babel 提供的一个同步方法,用于将输入的代码字符串通过指定的插件和预设进行转换。我们通过这个方法可以很方便地使用自定义的 Babel 插件来处理和转换 JavaScript 代码。希望这个示例能帮助你更好地理解 Babel 插件的使用方式
一个 hello world 级别示例,希望对有所帮助,大家一起进步~~
示例源码地址:github.com/Aoyia/nqbi