1. 抽象语法树(Abstract Syntax Tree)
webpack
和 Lint
等很多的工具和库的核心都是通过 Abstract Syntax Tree
抽象语法数这个概念来实现对代码的检查、分析等操作的。通过了解抽象语法数这个概念,你也可以随手编写类似的工具
2. 抽象语法树用途
- 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
- 如JSLint、JSHint对代码错误或风格的检查,发现一些潜在的错误
- IDE的错误提示、格式化、高亮、自动补全等等
- 代码混淆压缩
- UglifyJS2等
- 优化变更代码,改变代码结构使达到想要的结构
- 代码打包工具webpack、rollup等等
- CommonJS、AMD、CMD、UMD等代码规范之间的转化
- CoffeeScript、TypeScript、JSX等转化为原生Javascript
3. 抽象语法树定义
这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这棵树,我们可以精准的走位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作
在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。
Javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化为AST来更适合程序分析,浏览器编译器一般会把源码转化为AST来进行进一步的分析等其他操作。
1 | var AST = "is Tree"; |
4. JavaScript Parser
- JavaScript Parser,把js源码转化为抽象语法树的解析器。
- 浏览器会把js源码通过解析器转化为抽象语法树,再进一步转化为字节码或直接生成机器码。
- 一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准
4.1 常用的JavaScript Parser有:
- esprima
- traceur
- acorn
- shift
4.2 esprima
- 通过
esprima
把源码转化为AST - 通过
estraverse
遍历并更新AST - 通过
escodegen
将AST重新生成源码 - astexplorer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let estraverse = require('estraverse')
let escodegen = require('escodegen')
let code = "function ast(){}";
let ast = esprima.parse(code);
console.log('ast', ast)
estraverse.traverse(ast, {
enter(node){
console.log('enter', node.type)
if (node.type === 'Identifier') {
node.name += '_enter'
}
},
leave(node){
console.log('leave', node.type)
if (node.type === 'Identifier') {
node.name += '_leave'
}
}
})
console.log('ast', ast)
let result = escodegen.generate(ast)
console.log('result', result)
5. 转换箭头函数
- 访问者模式Visitor对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同
- babel-core
- babel-types
- Babel 插件手册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// babel核心库,用来实现核心的转换引擎的
let babel = require('babel-core')
// 可以实现类型判断,生成ast零部件
let types = require('babel-types')
let code = `let sum = (a,b) =>a+b` // let sum = function(a,b){return a+b}
// 这个访问者可以对特定的类型的节点进行处理
let visitor = {
ArrowFunctionExpression(path){
let params = path.node.params;
let blockStatement = types.blockStatement([
types.returnStatement(path.node.body)
])
let func = types.functionExpression(null, params, blockStatement, false, false);
path.replaceWith(func);
}
}
let arrayPlugin = {visitor}
// 在babel内部先把代码转成AST,然后在进行遍历,
let result = babel.transform(code,{
plugins: [
arrayPlugin
]
})
console.log(result.code)
6. babel插件
预计算简单表达式的插件
1 | const result = 1 + 2; |
1 | const result = 3; |
1 | let code = `const result = 1 + 2` |
7. webpack babel插件
7.1 实现按需加载
- Lodash
1
import { join, flatten } from 'ladash
转换为
1 | import join from "lodash/join" |
7.2 babel配置
transform-runtime
Babel默认只转换新的 JavaScript 语法,而不是转换新的 API。例如,literator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转义,启用插件babel-plugin-transform-runtime后,Babel就会使用babel-runtime下的工具函数1
2
3
4
5
6
7{
"presets": ["react", "stage-0", "env"],
"plugins": [
["extract", {"library": "lodash"}],
["transform-runtime", {}]
]
}
编译顺序为首先
plugins
从左往右,然后presets
从右往左
7.3 babel插件
1 | let babel = require('babel-core'); |