理解 Babel 插件

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

理解 Babel 插件

前言

相信目前常与 ES6 代码打交道的同学对 Babel 应该不会陌生,在 ES6 代码被编译转化为 ES5 代码的过程中,Babel 插件显得尤为重要,我们最后经由 Babel 生成的代码取决于插件在这一层中做了什么事,在探索这其中的过程之前,我们先来了解下一些所需的基础知识。

抽象语法树

Babel 的工作流可以用下面一张图来表示,代码首先经由 babylon 解析成抽象语法树(AST),后经一些遍历和分析转换(主要过程),最后根据转换后的 AST 生成新的常规代码。

在这其中,理解清楚 AST 十分重要,我们之所以需要将代码转换为 AST 也是为了让计算机能够更好地进行理解。我们可以来看看下面这段代码被解析成 AST 后对应的结构图:

function abs(number) {
if (number >= 0) { // test
return number; // consequent
} else {
return -number; // alternate
}
}

所有的 AST 根节点都是 Program 节点,从上图中我们可以看到解析生成的 AST 的结构的各个 Node 节点都很细微,Babylon AST 有个文档对每个节点类型都做了详细的说明,你可以对照各个节点类型在这查找到所需要的信息。在这个例子中,我们主要关注函数声明里的内容, IfStatement 对应代码中的 if...else 区块的内容,我们先对条件(test)进行判断,这里是个简单的二进制表达式,我们的分支也会从这个条件继续进行下去,consequent代表条件值为 true 的分支,alternate 代表条件值为 false 的分支,最后两条分支各自在 ReturnStatement 节点进行返回。

了解 AST 各个节点的类型是后续编写插件的关紧,AST 通常情况下都是比较复杂的,上述一段简单的函数定义也生成了比较大的 AST,对于一些复杂的程序,我们可以借助 astexplorer 来帮我们分析 AST 的结构。

遍历节点

在插件里进行节点遍历需要先了解 visitor 和 path 的概念,前者相当于从众多节点类型中选择开发者所需要的节点,后者相当于对节点之间的关系的访问。

visitor

Babel 使用 babel-traverse 进行树状的遍历,对于 AST 树上的每一个分支我们都会先向下遍历走到尽头,然后向上遍历退出遍历过的节点寻找下一个分支。Babel 提供我们一个 visitor 对象供我们获取 AST 里所需的具体节点来进行访问,比如我只想访问 if...else 生成的节点,我们可以在 visitor 里指定获取它所对应的节点:

const visitor = {
IfStatement() {
console.log('get if');
}
};

继续上述所说的遍历,其实这种遍历会让每个节点都会被访问两次,一次是向下遍历代表进入(enter),一次是向上退出(exit)。因此实际上每个节点都会有 enter 和 exit 方法,在实际操作的时候需要注意这种遍历方式可能会引起的一些问题,上述例子是省略掉 enter 的简写。

const visitor = {
IfStatement: {
enter() {},
exit() {}
}
}

path

visitor 模式中我们对节点的访问实际上是对节点路径的访问,在这个模式中我们一般把 path 当作参数传入节点选择器中。path 表示两个节点之间的连接,通过这个对象我们可以访问到节点、父节点以及进行一系列跟节点操作相关的方法(类似 DOM 的操作)。

var babel = require('babel-core');
var t = require('babel-types');

const code = `d = a + b + c`;

const visitor = {
Identifier(path) {
console.log(path.node.name); // d a b c
}
};

const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
});

替换节点

具备了 AST 相关知识和了解 visitor、path 后,就可以编写一个简单的 Babel 插件了。我们要把上述的 abs 函数换成原生支持的 Math.abs 来进行调用 。

首先我们先解析下 abs(-8) 的 AST 结构,直接从表达式语句(ExpressionStatement)开始:

{
type: "ExpressionStatement",
expression: {
type: "CallExpression",
callee: {
type: "Identifier",
name: "abs"
},
arguments: [{
type: "UnaryExpression",
operator: "-",
prefix: true,
arguments: {
type: "NumericLiteral",
value: 8
}
}]
}
}

我们可以看到表达式语句下面的 expression 主要是函数调用表达式(CallExpression),因此我们也需要创建一个函数调用表达式,此外,Math.abs 是一个二元操作表达式,属于 MemberExpression 类型。上述两个 AST 节点我们可以借助 babel-types 里提供的一些方法帮我们快速创建。

// 创建函数调用表达式
t.CallExpression(
// 创建对象属性引用
t.MemberExpression(t.identifier('Math'), t.identifier('abs')),
// 原始节点函数调用参数
path.node.arguments
)

最后我们需要对此次函数调用不符合的节点进行过滤,过滤掉名字不等于 abs 的函数调用,因为 Babel 在遍历的过程是递归的,如果不过滤做限制的话,程序将会一直运行最终报调用栈超过阈值的错误。

RangeError: unknown: Maximum call stack size exceeded

最终代码如下:

var babel = require('babel-core');
var t = require('babel-types');

const code = `abs(-8);`;

const visitor = {
CallExpression(path) {
if (path.node.callee.name !== 'abs') return;

path.replaceWith(t.CallExpression(
t.MemberExpression(t.identifier('Math'), t.identifier('abs')),
path.node.arguments
));
}
};

const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
});

// Math.abs(-8)
console.log(result.code);

上述例子使用了 transform api 直接解析转换生成了新的代码,另外在单独编写 Babel 插件的时候,暴露的参数里一般都含有常用的 babel-types 对象供使用。

export default function({ types: t }) {
return {
visitor: {}
};
}

总结

通过编写 Babel 插件我们能对 AST 有一定的了解,另外,我认为现阶段 Babel 插件不仅仅止于对 ES6 代码的转换上,npm 上有一系列的插件覆盖了许多适合的应用场景,后续具有一定的探索性。

Reference

本文作者: 波本
转载自: http://taobaofed.org/blog/2016/09/29/babel-plugins/
目录
相关文章
|
4月前
|
前端开发
Vite——如何配置使用sass
Vite——如何配置使用sass
285 0
|
JavaScript 前端开发 编译器
分享:Babel7的配置
分享:Babel7的配置
291 0
|
23天前
|
JavaScript 前端开发
如何在项目中集成 Babel?
如何在项目中集成 Babel?
30 3
|
23天前
|
JavaScript 前端开发
Babel 插件的作用是什么?
Babel 插件的作用是什么?
|
1月前
|
JavaScript 前端开发
将 Babel 插件应用于实际项目中
【10月更文挑战第25天】如果在应用插件过程中出现问题,可以检查 Babel 配置是否正确、插件的依赖是否安装完整、构建工具的集成是否正确等,逐步排查和解决问题。通过以上步骤,就可以将 Babel 插件成功应用到实际项目中,实现特定的代码转换和功能增强。
|
1月前
|
开发框架 自然语言处理 JavaScript
babel 原理,怎么写 babel 插件
【10月更文挑战第23天】要深入理解和掌握如何编写 Babel 插件,需要不断实践和探索,结合具体的项目需求和代码结构,灵活运用相关知识和技巧。你还可以进一步扩展和深入探讨各个方面的内容,提供更多的实例和细节,以使文章更加丰富和全面。同时,关注 Babel 插件开发的最新动态和研究成果,以便及时了解其发展和变化。
|
1月前
怎么写的 babel 插件
【10月更文挑战第25天】我们可以编写一个简单的Babel插件,并根据实际需求对其进行扩展和修改,以满足特定的代码转换需求。在实际编写Babel插件时,还需要对Babel的AST结构和各种节点类型有更深入的了解,以便能够更灵活地处理各种复杂的代码转换场景。
|
6月前
|
JavaScript 前端开发 API
你好,babel
你好,babel
|
7月前
|
编解码 JSON JavaScript
babel的学习
babel的学习
45 0
|
自然语言处理 前端开发 JavaScript
Babel 的工作原理以及怎么写一个 Babel 插件
Babel 的工作原理以及怎么写一个 Babel 插件
215 0