前一篇文章讲解了什么是AST,但是没有说明如何操作AST,下面来讲解下如何使用babel来操作AST,首先看一下流程图:
babel操作AST的流程如上图,首先将js代码转化为AST语法树,然后利用一些特定的库去修改AST语法树,然后得到新的语法树,将新的语法树转化为js代码,这样就可以转化js代码了。
需要操作 AST 代码,这里,我们需要借助两个库,分别是 @babel/core 和 babel-types。 其中 @babel/core 是 babel 的核心库,用来实现核心转换引擎,babel-types 类型判断,用于生成AST零部件。通过 npm i @babel/core babel-types -D 安装依赖
我们用一个例子来说明babel的使用方法,首先源代码如下:
代码语言:javascript复制const sum=(a,b)=>a b;
转化后的代码如下:
代码语言:javascript复制const sum = function(a,b){
return a b;
}
本质就是讲箭头函数转化为function函数。
利用这个网站来分析下两端代码的AST有什么不同:
第一段代码的AST:
代码语言:javascript复制// 源代码的 AST
{
"type": "Program",
"start": 0,
"end": 21,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 21,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 20,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "sum"
},
"init": {
"type": "ArrowFunctionExpression",
"start": 10,
"end": 20,
"id": null,
"expression": true,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 11,
"end": 12,
"name": "a"
},
{
"type": "Identifier",
"start": 13,
"end": 14,
"name": "b"
}
],
"body": {
"type": "BinaryExpression",
"start": 17,
"end": 20,
"left": {
"type": "Identifier",
"start": 17,
"end": 18,
"name": "a"
},
"operator": " ",
"right": {
"type": "Identifier",
"start": 19,
"end": 20,
"name": "b"
}
}
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
第二段代码的AST:
代码语言:javascript复制// 目标代码的 `AST`
{
"type": "Program",
"start": 0,
"end": 48,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 48,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 47,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "sum"
},
"init": {
"type": "FunctionExpression",
"start": 12,
"end": 47,
"id": null,
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 22,
"end": 23,
"name": "a"
},
{
"type": "Identifier",
"start": 25,
"end": 26,
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 28,
"end": 47,
"body": [
{
"type": "ReturnStatement",
"start": 32,
"end": 45,
"argument": {
"type": "BinaryExpression",
"start": 39,
"end": 44,
"left": {
"type": "Identifier",
"start": 39,
"end": 40,
"name": "a"
},
"operator": " ",
"right": {
"type": "Identifier",
"start": 43,
"end": 44,
"name": "b"
}
}
}
]
}
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
仔细对比发现,我们发现主要不同就在于 init 这一段,一个是 ArrowFunctionExpression , 另一个是 FunctionExpression , 我们只需要将 ArrowFunctionExpression 下的内容改造成跟 FunctionExpression 即可。我们在修改第一段代码的AST时着重修改这里,如何修改呢?看代码:
代码语言:javascript复制//babel 核心库,用来实现核心转换引擎
const babel = require('@babel/core')
//类型判断,生成AST零部件
const types = require('babel-types')
//源代码
const code = `const sum=(a,b)=>a b;` //目标代码 const sum = function(a,b){ return a b }
这里我们需要用到 babel 中的 transform 方法,它可以将 js 代码转换成 AST ,过程中可以通过使用 plugins 对 AST 进行改造,最终生成新的 AST 和 js 代码,其整个过程用网上一个比较贴切的图就是:
我们需要使用babel的transform方法,使用方法如下:
代码语言:javascript复制//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code
let result = babel.transform(code,{
plugins:[
{
visitor
}
]
})
transform的第一个参数为code也就是源代码,第二个参数为一个对象,对象的属性有plugins和preset两个属性,这里我们只用plugins,plugins是一个数组,数组的每一项是一个对象,对象的属性为visiter
它是一个插件对象,可以对特定类型的节点进行处理,这里我们需要处理的节点是ArrowFunctionExpression,它常见的配置方式有两种: 一种是单一处理,结构如下,其中 path 代表当前遍历的路径 path.node 代表当前变量的节点
代码语言:javascript复制let visitor = {
ArrowFunctionExpression(path){
}
}
另一种是用于输入和输出双向处理,结构如下,参数 node 表示当前遍历的节点
代码语言:javascript复制let visitor = {
ArrowFunctionExpression : {
enter(node){
},
leave(node){
}
}
}
这里我们使用第一种:
通过分析目标代码的 AST,我们发现,需要一个 FunctionExpression , 这时候我们就需要用到 babel-types ,它的作用就是帮助我们生产这些节点 我们通过其 npm 包的文档查看,构建一个 FunctionExpression 需要的参数如下:
参照 AST 我们可以看到其 id 为 null,params 是原 ArrowFunctionExpression 中的 params,body 是一个blockStatement,我们也可以通过查看 babel-types 文档,用 t.blockStatement(body, directives) 来创建,依次类推,照猫画虎,最终得到的结果如下:
代码语言:javascript复制 //原 params
let params = path.node.params;
//创建一个blockStatement
let blockStatement = types.blockStatement([
types.returnStatement(types.binaryExpression(
' ',
types.identifier('a'),
types.identifier('b')
))
]);
//创建一个函数
let func = types.functionExpression(null, params, blockStatement, false, false);
然后通过path.replaceWith(func); 将其替换即可:
代码语言:javascript复制//babel 核心库,用来实现核心转换引擎
const babel = require('@babel/core')
//类型判断,生成AST零部件
const types = require('babel-types')
//源代码
const code = `const sum=(a,b)=>a b;` //目标代码 const sum = function(a,b){ return a b }
//插件对象,可以对特定类型的节点进行处理
let visitor = {
//代表处理 ArrowFunctionExpression 节点
ArrowFunctionExpression(path){
let params = path.node.params;
//创建一个blockStatement
let blockStatement = types.blockStatement([
types.returnStatement(types.binaryExpression(
' ',
types.identifier('a'),
types.identifier('b')
))
]);
//创建一个函数
let func = types.functionExpression(null, params, blockStatement, false, false);
//替换
path.replaceWith(func);
}
}
//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code
let result = babel.transform(code,{
plugins:[
{
visitor
}
]
})
console.log(result.code);
以上便是利用bebel来操作AST的操作,希望对你有所帮助。