@babel-AST常用方法
一、js对应AST
-
obj
变量名对应Identifier
,常量串对应StringLiteral
,数字对应NumericLiteral
。 -
CallExpression
主要关注callee
和arguments
属性,分别表示被调用的函数和参数列表。 -
MemberExpression
主要关注object
、property
和computed
属性,分别表示对象,属性和是否是计算属性。Dot Notation
和Array Notation
的computed
分别为false
和true
。
二、@babel/parser方法
-
parser.parse(code, [{options}])
-
parser.parseExpression(code, [{options}])
:考虑到了性能问题,解析单个 JavaScript 表达式。部分可选参数
options
:参数 描述 allowImportExportEverywhere
默认 import
和export
声明语句只能出现在程序的最顶层,设置为true
则在任何地方都可以声明allowReturnOutsideFunction
默认如果在顶层中使用 return
语句会引起错误,设置为true
就不会报错sourceType
默认为 script
,当代码中含有import
、export
等关键字时会报错,需要指定为module
errorRecovery
默认如果 babel 发现一些不正常的代码就会抛出错误,设置为 true
则会在保存解析错误的同时继续解析代码,错误的记录将被保存在最终生成的 AST 的 errors 属性中,当然如果遇到严重的错误,依然会终止解析
三、@babel/generator方法
可以将 AST 还原成 JavaScript 代码,提供了一个 generate
方法:generate(ast, [{options}], code)
。
部分可选参数 options
:
参数 | 描述 |
---|---|
auxiliaryCommentBefore |
在输出文件内容的头部添加注释块文字 |
auxiliaryCommentAfter |
在输出文件内容的末尾添加注释块文字 |
comments |
输出内容是否包含注释 |
compact |
输出内容是否不添加空格,避免格式化 |
concise |
输出内容是否减少空格使其更紧凑一些 |
minified |
是否压缩输出代码 |
retainLines |
尝试在输出代码中使用与源代码中相同的行号 |
四、@babel/traverse方法
通常和 visitor
一起使用,visitor
是一个对象,这个名字是可以随意取的,visitor
里可以定义一些方法来过滤节点
path
对象,该对象的类型是 NodePath
,该对象有非常多的属性,以下介绍几种最常用的:
属性 | 描述 |
---|---|
toString() |
当前路径的源码 |
node |
当前路径的节点 |
parent |
当前路径的父级节点 |
parentPath |
当前路径的父级路径 |
type |
当前路径的类型 |
-
replaceWith
:用一个节点替换另一个节点; -
replaceWithMultiple
:用多个节点替换另一个节点; -
replaceWithSourceString
:将传入的源码字符串解析成对应 Node 后再替换,性能较差,不建议使用; -
replaceInline
:用一个或多个节点替换另一个节点,相当于同时有了前两个函数的功能。
五、@babel/types方法
主要用于构建新的 AST 节点或者节点类型判断
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require("@babel/types")
const code = `
const a = 1;
`
const ast = parser.parse(code)
const visitor = {
//从VariableDeclaration这个节点开始遍历添加
VariableDeclaration(path) {
left = types.identifier("b")
right = types.identifier("a")
//将左右节点用+符号拼接
let BinaryExpression = types.binaryExpression("+", left, right)
//添加到return节点
let returnStatement = types.returnStatement(BinaryExpression)
//添加到作用域{}节点
let BlockStatement = types.blockStatement([returnStatement])
//添加函数节点,函数名为func,参数为a,b
declaration = types.functionDeclaration(types.identifier("func"), [types.identifier("a"),types.identifier("b")], BlockStatement)
path.insertAfter(declaration)
//将a节点和数字节点用*符号拼接
left = types.binaryExpression("*", types.identifier("a"), types.numericLiteral(5))
right = types.numericLiteral(1)
//将左右节点用+符号拼接
let value = types.binaryExpression("+", left, right)
//创建一个变量b,将value赋值于b
let declarator = types.variableDeclarator(types.identifier("b"), value)
declaration = types.variableDeclaration("const", [declarator])
path.insertAfter(declaration) //插入节点
path.stop() //停止遍历节点
},
}
traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)
const parser = require("@babel/parser");
const generate = require("@babel/generator").default
const traverse = require("@babel/traverse").default
const types = require('@babel/types');
const code = `
const example = function () {
let a;
if (false) {
a = 1;
} else {
if (1) {
a = 2;
}
else {
a = 3;
}
}
return a;
};
`
const ast = parser.parse(code)
const visitor = {
enter(path) {
if (types.isBooleanLiteral(path.node.test) || types.isNumericLiteral(path.node.test)) {
//判断一定为真,就替换
//if 对应的是 consequent 节点,else 对应的是 alternate 节点
if (path.node.test.value) {
path.replaceInline(path.node.consequent.body); //方法将节点替换成计算结果生成的新节点
} else {
if (path.node.alternate) {
path.replaceInline(path.node.alternate.body);
} else {
path.remove()
}
}
}
}
}
traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)