个人主页:不叫猫先生
♂️作者简介:专注于前端领域各种技术,热衷分享,关注我会给你带来一些不一样的认知和成长。
个人签名:不破不立
抽象语法树(Abstract Syntax Tree,AST),是源代码(不仅限于JavaScript,同时还应用于其他语言,例如: Python,Rust等)语法结构的⼀种抽象表示。它以树状的形式表现编程语⾔的语法结构,树上的每个节点都表示源代码中的⼀种结构。
AST 运⽤⼴泛,⽐如:
AST在线预览网站
Bable AST官网
本文示范数据:
window.a = 3;
let a = 2, b = 3;
let obj = {
name: '张三',
age: "18",
interest: ["篮球", "羽毛球"],
add: function (a, b) {
setTimeout(() => {
})
return a + b + 1000
},
multiplication: function (a, b) {
if (a) {
b = a
} else {
}
return a * b + 1000
}
}
.
来引用成员,property 应该为一个 Identifier 节点,如果 computed 属性为 true,则是 [] 来进行引用,即 property 是一个Expression 节点,名称是表达式的结果值。window.a对应的AST如下:elements
属性是一个数组,表示数组的多个元素,每一个元素都是一个表达式节点。let a = 2,b=3
。Babel 是一个 JavaScript 的转译器,其执行过程就是一个编译转换的过程。作为一个js转译器,babel暴露了很多 api,利用这些 api 可以完成源代码到 AST 的 parse,AST 的遍历与处理以及目标代码的生成。babel将这些功能的实现放到了不同的包里面,下面逐一介绍。
@babel/parser
解析源码得到AST@babel/traverse
遍历 AST节点@babel/types
用于构建AST节点和判断AST节点类型@babel/generate
打印 AST,生成目标代码和 sorucemap
(即将ast转换成js代码)babel的处理步骤:主要有三个阶段:解析(parse), 转换 (transform),生成(generate)。
parse
将源码转成 AST,用到@babel/parser
模块。
transform
对AST 进行遍历,在此过程中对节点进行添加、更新及移除等操作。因此这是bebel处理代码的核心步骤,是我们的讨论重点,主要使用@babel/traverse
和@babel/types
模块。
generate
打印 AST 成目标代码并生成 sourcemap
,用到@babel/generate
模块。
接下来我们来重点了解转换这一步,上面我们提到,转换的第一步是遍历AST。说到这里就不得不提到一个设计模式——访问者模式。
访问者模式,即将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式,简单来说,就是定义了用于在一个树状结构中获取具体节点的方法。当访问者把它用于遍历中时,每当在树中遇见一个对应类型时,都会调用该类型对应的方法。
从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题,并且采用了Babel Monorepo风格的仓库。在测试之前需要安装@babel/core
、@babel/cli
、@babel/preset-env
yarn add @babel/core @babel/cli -D
@babel/core
是Babel 实现转换的核心,他是依赖能力更底层的 @babel/parser
、 @babel/code-frame
、@babel/generator
、@babel/traverse
、@babel/types
等。
@babel/cli
是 Babel 提供的命令行,它可以在终端中通过命令行方式运行,编译文件。
@babel/preset-env'
Babel 只是一个’编译器’你需要告诉他转换规则,需要在transformer,利用我们配置好的 plugins/presets把 Parser生成的 AST转变为新的 AST,即@babel/preset-env'
就是一套转换规则集合。
下图为转换流程let
声明转换为var
声明
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generator = require('@babel/generator');
const transToLet = code => {
const ast = parser.parse(code);
// 访问者对象
const visitor = {
// 遍历声明表达式
VariableDeclaration(path) {
if (path.node.type === 'VariableDeclaration') {
// 替换
if (path.node.kind === 'var') {
path.node.kind = 'let';
}
}
},
};
traverse.default(ast, visitor);
// 生成代码
const newCode = generator.default(ast, {}, code).code;
return newCode;
};
const code = `const a = 1
var b = 2
let c = 3`;
console.log(transToLet(code))
通过parse解析得到了ast
,具体如下:
Node {
type: 'File',
start: 0,
end: 31,
loc: SourceLocation {
start: Position { line: 1, column: 0, index: 0 },
end: Position { line: 3, column: 9, index: 31 },
filename: undefined,
identifierName: undefined
},
errors: [],
program: Node {
type: 'Program',
start: 0,
end: 31,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
sourceType: 'script',
interpreter: null,
body: [ [Node], [Node], [Node] ],
directives: []
},
comments: []
}
执行
node babel.js
输出
const a = 1;
let b = 2;
let c = 3;
可见var
都变成了let
该插件为superLog
,源码如下:
const generator = require('@babel/generator');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const types = require('@babel/types');
const trans = require('./trans.js')
const addNode = code => {
const ast = parser.parse(code);
// 访问者对象
const visitor = {
// 遍历调用表达式
CallExpression(path) {
const { callee } = path.node;
if (types.isCallExpression(path.node) && types.isMemberExpression(callee)) {
const { object, property } = callee;
if (object.name === 'console' && property.name === 'log') {
const newArg = trans(path.node.arguments);
path.node.arguments = [...newArg];
}
}
},
};
traverse.default(ast, visitor);
// 生成代码
const newCode = generator.default(ast, {}, code).code;
return newCode;
};
//callee
Node {
type: 'MemberExpression',
start: 86,
end: 97,
loc: SourceLocation {
start: Position { line: 8, column: 0, index: 86 },
end: Position { line: 8, column: 11, index: 97 },
filename: undefined,
identifierName: undefined
},
object: Node {
type: 'Identifier',
start: 86,
end: 93,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: 'console'
},
name: 'console'
},
computed: false,
property: Node {
type: 'Identifier',
start: 94,
end: 97,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: 'log'
},
name: 'log'
}
}
//path.node.arguments的值
[
Node {
type: 'Identifier',
start: 98,
end: 99,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: 'a'
},
name: 'a'
},
Node {
type: 'MemberExpression',
start: 101,
end: 108,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
object: Node {
type: 'MemberExpression',
start: 101,
end: 106,
loc: [SourceLocation],
object: [Node],
computed: false,
property: [Node]
},
computed: false,
property: Node {
type: 'Identifier',
start: 107,
end: 108,
loc: [SourceLocation],
name: 'b'
}
},
Node {
type: 'CallExpression',
start: 110,
end: 118,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
callee: Node {
type: 'MemberExpression',
start: 110,
end: 116,
loc: [SourceLocation],
object: [Node],
computed: false,
property: [Node]
},
arguments: []
}
]
新建trans,js文件
const types = require('@babel/types');
// 获取父辈节点并拼接
const getNodeName = node => {
const getPreValue = node => {
if (node.object && node.property) {
return `${node.property.name}.${getPreValue(node.object)}`;
} else {
return node.name;
}
};
return getPreValue(node)
.split('.')
.reverse()
.map((item, index, arr) => (index === arr.length - 1 ? item : `${item}.`))
.join('');
};
const actionMap = {
// 调用表达式
CallExpression: node => getNodeName(node.callee),
// 标识符
Identifier: node => node.name,
// 成员表达式
MemberExpression: node => getNodeName(node),
// 字符串
StringLiteral: node => '',
};
const trans = list => {
//初始化一个数组长度为传的参数2倍
let res = new Array(list.length * 2).fill(null);
list.forEach((node, index) => {
res[index * 2 + 1] = node;
console.log(node.type,'1111111111111111')
console.log(node,'22222222222222222')
const strNodeName = actionMap[node.type](node);
res[index * 2] = strNodeName ? types.stringLiteral(`${strNodeName}`) : '';
});
return res;
};
module.exports = trans;
其中node.type分别是:
Identifier
MemberExpression
CallExpression
最后打印结果为
const obj = {
a: {
b: 'xiaom'
},
fn: () => null
};
const a = 2;
console.log("a", a, "obj.a.b", obj.a.b, "obj.fn", obj.fn());