编写一个Babel插件
2023-08-21 本文已影响0人
如果俞天阳会飞
搭建一个rollup
- 新建一个 babel-plugin-decimal文件夹用来开发Babel插件
标准规范babel-plugin-xxxxx 开头
npm init
npm i rollup @rollup/plugin-babel
npm i decimal.js -S
{
"name": "babel-plugin-decimal",
"version": "1.0.1",
"description": "",
"main": "lib/index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c"
},
"author": "",
"license": "ISC",
"files": [
"lib/*",
"src/*"
],
"devDependencies": {
"@babel/preset-env": "^7.22.10",
"@rollup/plugin-babel": "^6.0.3",
"@babel/core": "^7.22.10",
"rollup": "^3.28.0"
},
"peerDependencies": {
"@babel/core": "^7.22.8",
"@babel/preset-env": "^7.22.7"
},
"dependencies": {
"decimal.js": "^10.4.3"
}
}
- 然后在根目录下新建 rollup.config.js 文件,用来配置 rollup 打包
import babel from '@rollup/plugin-babel';
export default {
input: 'src/index.js',
output: {
// name: 'decimal',
file: 'lib/index.js',
format: 'cjs'
},
plugins: [
babel({
babelHelpers: 'bundled',
presets: [['@babel/preset-env', {
"targets": {
"edge": '17',
"firefox": '60',
"chrome": '67',
"safari": '11.1'
}
}]]
})
]
}
- 创建src文件夹新建index.js文件
什么是抽象语法树(AST)?
我们可以借助一个网站,来一睹抽象语法树的真容~ https://astexplorer.net/
- 每一个代码片段都有属于自己的节点类型
- 代码最外层的节点类型为 Program
- 像 0.1+0.2 这种表达式,节点类型为 BinaryExpression
-
BinaryExpression节点里会有几个重要的东西
- operaor:运算符号
- left:左边的数字
- right: 右边的数字
其实抽象语法树的节点类型有很多:
- 标识符(Identifier):表示变量、函数名等标识符的节点
- 字面量(Literal):表示字面量值,如字符串、数字、布尔值等
- 表达式语句(ExpressionStatement):表示包含表达式的语句节点
- 赋值表达式(AssignmentExpression):表示赋值操作的表达式节点,如 x = 5
- 二元表达式(BinaryExpression):表示包含二元操作符的表达式节点,如 x + y
- 一元表达式(UnaryExpression):表示包含一元操作符的表达式节点,如 -x
- 函数声明(FunctionDeclaration):表示函数声明的节点,包括函数名、参数和函数体
- 变量声明(VariableDeclaration):表示变量声明的节点,包含变量名和可选的初始值
- 条件语句(IfStatement):表示 If 条件语句的节点,包括条件表达式、if 分支和可选的 else 分支
- 循环语句(WhileStatement、ForStatement):表示循环语句的节点,分别代表 While 循环和 For 循环
- 对象字面量(ObjectLiteral):表示对象字面量的节点,包含对象属性和属性值
- 数组字面量(ArrayLiteral):表示数组字面量的节点,包含数组元素
- 函数调用(CallExpression):表示函数调用的节点,包含调用的函数名和参数列表
- 返回语句(ReturnStatement):表示返回语句的节点,包含返回的表达式
其实,我们平时在 webpack 开发时会接触到一系列的插件,他们的功能比如有
- 去除console.log
- 压缩代码
- 去除注释
其实他们的原理整体上都是一致的,分为三步:
- 第一步 代码转换成抽象语法树
- 第二步 使用Babel为我们提供的方法,对语法树进行增删改查
- 第三步 将处理后的语法树重新转换成代码
但是第一步和第三步我们不需要管,我们只需要完成第二步中的增删改查操作即可
image.png注意点:在第二步中,babel 会对抽象语法树进行深度遍历,遍历到目标节点后,又会重新回到上层节点去重新遍历下一个目标节点,所以一个节点会被遍历两次,一来一回 进去是 enter 回去是 exit
插件基本代码结构
export default function ({ template, types: t }) {
return {
visitor: {
Program: {
exit: function (path) {
}
},
BinaryExpression: {
exit: function (path) {
}
}
}
}
}
开发一个 babel 插件,文件必须默认返回一个函数,接收一个对象参数,里面有个属性我们需要用到
-
template: 是@babel/template的一个方法,他能使用模板的方式生成AST节点
-
vistor: 你可以理解为修改AST节点的入口
-
Program、BinaryExpression: 你需要修改的AST节点类型
-
exit 就是刚刚说的 一来一回 中的,回
-
path 就是被遍历到的AST节点对象
插件完全实现
const DECIMAL = 'Decimal';
const OPERATIONS_MAP = {
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
}
const OPERATIONS_KEY = Object.keys(OPERATIONS_MAP)
export default function ({template: template}) {
const requireDecimal = template(`const ${DECIMAL}=require('decimal.js')`);
// babel-template:可以通过字符串的形式生成一个ast
// 将运算表达式转换为decimal函数的节点模板
const temp = template(`new ${DECIMAL}(LEFT).OPERATION(RIGHT).toNumber()`)
return {
visitor: {
Program: {
exit: function (path) {
// 调用方法,往子节点body
// 中插入 const Decimal = require('decimal.js')
path.unshiftContainer('body', requireDecimal())
}
},
BinaryExpression: {
exit: function (path) {
const operator = path.node.operator;
if (OPERATIONS_KEY.includes(operator)) {
// 调用方法替换节点
path.replaceWith(
// 传入 operator left right
temp({
LEFT: path.node.left,
RIGHT: path.node.right,
OPERATION: OPERATIONS_MAP[operator]
})
)
}
}
}
}
}
}
打包 发布NPM
当开发完成后,我们先 npm run build进行打包
然后运行 npm publish 发布到 NPM 上
项目使用
首先安装 babel-plugin-decimal
npm i babel-plugin-decimal
只需要在项目中的 .babelrc 或者 babel.config.js 中加入 babel-plugin-decimal即可
{
"presets": ["@babel/preset-env"],
"plugins": ["babel-plugin-sx-accuracy"]
}