webpack 和 esLint 等很多工具和库的核心都是通过 Abstract Syntax Tree(抽象语法树)来实现对代码的检查,分析等操作。通过了解抽象语法树这个概念,自己也会对类似的工具有一个更深入的认识。
代码语法检查,代码风格检查,代码高亮,代码错误提示,代码自动补全等等
代码混淆压缩
优化变更代码,改变代码结构达到自己想要的结构
通过
JavaScript Parser
把代码转化为一颗抽象语法书(AST),这棵树定义了代码的结构,,通过操纵这棵树,我们可以精准的定位到声明语句,赋值语句,运算语句,实现对代码的分析,优化,变更等操作
JavaScript
的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解.所以需要转化为AST来更适合程序的分析,浏览器编译一般会把源码转化为AST来进行进一步的分析等其他操作function fn () {
}
{
"type": "Program", // 类型 是一个程序
"body": [ // body 代码体的意思
{
"type": "FunctionDeclaration", // 函数声明
"id": {
// 标识符
"type": "Identifier",
"name": "fn", // name 即使我们定义的名称
"range": [// 声明代码的位置,栗子:function fn
9,
11
]
},
"params": [// 参数
{
"type": "Identifier",
"name": "a",
"range": [
13,
14
]
}
],
"body": {
// 函数体
"type": "BlockStatement", // 类型,是一个块状代码
"body": [], // 函数体内的代码语句
"range": [// 他的代码提的返回
15,
17
]
},
"generator": false, // 是否是generator函数
"expression": false, //
"async": false, // 是否是async步函数
"range": [
0,
17
]
}
],
"sourceType": "module", // 源代码类型 是一个模块
"range": [// 返回 代码的总体偏移量
0,
17
]
}
esprima可以将一个源代码语法转换为AST
npm i esprima
const esprima = require('esprima')
let code = 'function ast () {}'
let ast = esprima.parse(code)
console.log(ast)
// 答应结果如下
Script {
type: 'Program',
body:
[ FunctionDeclaration {
type: 'FunctionDeclaration',
id: [Identifier],
params: [],
body: [BlockStatement],
generator: false,
expression: false,
async: false } ],
sourceType: 'script' }
estraverse遍历语法书进行更新AST
const esprima = require('esprima')
const estraverse = require('estraverse')
// const
let code = 'function ast () {}'
let ast = esprima.parse(code)
estraverse.traverse(ast, {
enter (node, parent) {
if (node.type === 'Identifier') {
node.name += ' enter'
}
console.log('enter', node.type)
},
leave (node, parent) {
if (node.type === 'Identifier') {
node.name += ' leave'
}
console.log('leave', node.type)
}
})
// 打印结果如下
enter Identifier
leave Identifier
enter BlockStatement
leave BlockStatement
leave FunctionDeclaration
leave Program
escodegen将AST重新生成源码
const esprima = require('esprima')
const estraverse = require('estraverse')
const escodegen = require('escodegen')
let code = 'function ast () {}'
let ast = esprima.parse(code)
estraverse.traverse(ast, {
enter (node, parent) {
if (node.type === 'Identifier') {
node.name += '_enter'
}
},
leave (node, parent) {
if (node.type === 'Identifier') {
node.name += '_leave'
}
}
})
let result = escodegen.generate(ast)
console.log(result) // 重新生成后的代码
// 输出如下:
function ast_enter_leave() {
}
// console.log(ast)
访问者模式Visitor对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行的操做也不同步
转换代码
visitor有两种写法
// 第一种写法
let visitor = {
ArrowFunctionExpression: {
enter (node, parent) {
console.log('enter', node.type)
},
leave (node, parent) {
console.log('leave', node.type)
}
}
}
// 第二种写法
let visitor = {
ArrowFunctionExpression (path) {
// console.log(path)
let params = path.node.params
let body = types.blockStatement([
types.returnStatement(path.node.body)
])
let fn = types.functionExpression(null, params, body, false, false)
path.replaceWith(fn)
}
}
匹配规则,,就是变量名等于我们插件的函数名
// babel插件核心,用来实现核心的转换引擎
const babel = require('babel-core')
// 可以实现类型转换,生成AST的零部件
const types = require('babel-types')
let code = `let sum = (a, b) => a + b;`
// 创建访问者可以对特定类型的节点进行处理
let visitor = {
// 在遍历所有的语法书时,变量名等于我们插件的函数名,就会调用此插件
ArrowFunctionExpression (path) {
// console.log(path)
let params = path.node.params
let body = types.blockStatement([
types.returnStatement(path.node.body)
])
let fn = types.functionExpression(null, params, body, false, false)
path.replaceWith(fn)
}
}
// 创建arrow处理插件
let arrowPlugin = {
visitor }
// babel内部先把代码转换成AST,然后进行遍历,遍历到类型等于ArrowFunctionExpression, 交给插件arrayPlugin处理
// 将插件传入babel中,得到结果
let result = babel.transform(code, {
plugins: [
arrowPlugin
]
})
console.log('result', result.code)
/*
result let sum = function (a, b) {
return a + b;
};
*/