简单易懂的babel插件开发
babel
babel相信大家应该都不陌生,作为JS
编译器,Babel
接收输入的JS
代码,经过内部处理流程,最终输出修改后的JS
代码。
在Babel
内部,会执行如下步骤:
- 将
Input Code
解析为AST
(抽象语法树),这一步称为parsing
- 编辑
AST
,这一步称为transforming
- 将编辑后的
AST
输出为Output Code
,这一步称为printing
我们编写的babel插件就是在第二步transforming
来进行处理,最后编译输出
实现babel插件
一个好用的线上js转化ast的工具 => 在线js代码转换为AST
我们来看看babel插件是如何开发的,首先初始化一下,然后安装如下所需依赖
yarn init
yarn add @babel/cli @babel/core @babel/types -D
对应文件描述
test/compiled.js babel 编译后的代码的输出文件
test/index.js 初始原代码
test/plugin.js babel插件逻辑处理
.babelrc 插件配置文件
文件结构如下
我们需要在 package.json
增加命令,该命令就是通过babel
编译 index.js
文件并输出到文件compiled.js
"scripts": {
"example": "babel ./test/index.js --out-file ./test/compiled.js "
}
然后我们来看看典型的babel插件结构 plugins.js
文件
module.exports = function(e) {
return {
visitor: {
Identifier (path) {}
}
}
}
可以看到babel插件导出一个函数,函数里面return
一个对象,其中的visitor
属性也是最核心
babel 在使用 @babel/traverse
对 AST 进行深度遍历时,会 访问 每个 AST 节点,这个便是跟我们的 visitor
有关。babel 会在 访问 AST 节点的时候,调用 visitor
中对应 节点类型 的方法,这便是 babel 插件暴露给开发者的核心。
改变值
我们直接看个栗子
const val = 1
我们来看看上面例子对应的ast样子,可以通过上面提供的在线网址进行转化
可以看到上述代码块的节点类型是VariableDeclaration
,value对应的节点类型为NumericLiteral
,我们现在试着通过babel插件来改变一下val的值
// .babelrc
{
"plugins": ["./test/plugin"]
}
// index.js
const val = 1
// plugin.js
module.exports = function () {
return {
visitor: {
NumericLiteral(path) {
path.node.value = 2
}
}
}
}
在命令行中执行yarn example
,等执行完毕出现类似 Done in xxs
就代表编译成功,然后打开compiled.js
文件,可以看到显示const val = 2;
说明我们编写的插件有效
通过这个例子应该可以更加生动知道visitor
属性的作用,它会对ast进行深度遍历,匹配到对应的节点类型钩子函数进行逻辑处理。
消灭console
当我们在进行项目开发的时候,经常会通过console.log()
在控制台输出一些信息,但是我们又不希望在线上环境出现这些输出信息,我们可以写个babel插件来删除对应的console
。
const val = 1
console.log(val)
首先我们来看看上述代码对应的ast
只截图了console
对应的ast结构,可以看到console.log
对应的节点类型是CallExpression
(函数调用类型),并且其有callee
属性,其name值为console
插入一个知识点,怕大家对后面一些方法不清楚
babel库中path常用方法
- path.remove() 节点删除
- path.get(key) 获取当前路径下的子孙节点
- path.replaceWith(newNode) (单)节点替换函数
// index.js
const val = 1
console.log(val)
// plugin.js
module.exports = function () {
return {
visitor: {
CallExpression(path) {
// 获取callee节点
const callee = path.get('callee')
// // 获取callee节点下的object节点
const { node } = callee.get('object')
if (callee && node.name === 'console') {
path.remove()
}
}
}
}
}
执行yarn example
,执行完毕打开compiled.js
文件,可以看到只有const val = 1;
,说明我们编写的插件又成功了😊
可选链操作符
es6新增的 可选链操作符( ?.
)相信大家都听过或者用过,如果要在项目中使用它,需要安装babel插件(@babel/plugin-proposal-optional-chaining)并在.babelrc
文件中配置如下即可使用
{
"plugins": [
"@babel/plugin-proposal-optional-chaining"
]
}
接下来,我们也来试着开发一个可选链操作符插件
首先我们得先知道可选链操作符对应转化的代码是啥样
// 当a等于null/undefined时,则直接返回undefined否则返回a.b
const val = a?.b
⇩
⇩
const val = a === null || a === void 0 ? void 0 : a.b
知道长啥样,我们来看看可选链操作符对应的ast结构
可以看到可选链操作符对应的节点类型为OptionalMemberExpression
,object
节点对应的是变量a
,property
节点对应的是变量a
的属性b
。
要怎么将可选链对应的表达式进行转换呢?先看几个创建 AST 节点的示例方便我们后面转换
创建 AST 节点写法示例
// 创建三元表达式 示例 4 > 3 ? 4 : 3
t.conditionalExpression(
t.binaryExpression(
'>',
t.numericLiteral(4),
t.numericLiteral(3)
),
t.numericLiteral(4),
t.numericLiteral(3)
)
// 创建逻辑表达式 示例 num || 0
t.logicalExpression(
'||',
t.identifier('num'),
t.numericLiteral(0)
)
// 创建二元表达式 示例 1+2
t.binaryExpression(
'+', // 操作符。 还支持:==, ===, != 等
item1, // 左操作数
item2
)
// 创建获取对象的属性 示例 obj.name
t.memberExpression(
t.identifier('obj'),
t.identifier('name')
)
// 创建null
t.nullLiteral()
上面的示例足够我们来实现可选链操作符插件
// const val = a === null || a === void 0 ? void 0 : a.b
const t = require('@babel/types')
module.exports = function () {
const transCondition = (node) => {
return t.conditionalExpression( // 三元表达式
t.logicalExpression( // 逻辑表达式
'||',
t.binaryExpression('===', node.object, t.nullLiteral()), // 二元表达式
t.binaryExpression(
'===',
node.object,
t.unaryExpression('void', t.numericLiteral(0))
)
),
t.unaryExpression('void', t.numericLiteral(0)),
t.memberExpression( // 获取对象的属性a.b
node.object,
node.property
)
)
}
return {
visitor: {
OptionalMemberExpression(path) {
// 替换节点
path.replaceWith(transCondition(path.node))
},
},
}
}
为什么要用void 0 替代undefined
-
某些情况下用undefined判断存在风险,因undefined有被修改的可能性,但是void 0返回值一定是undefined
-
兼容性上void 0 基本所有的浏览器都支持
-
void 0比undefined字符所占空间少。
执行yarn example
,执行完毕打开compiled.js
文件,可以看到const val = a === null || a === void 0 ? void 0 : a.b;
,我们编写的插件又成功了😊😊😊
转载自:https://juejin.cn/post/7165912843315839012