什么是抽象语法树(Abstract Syntax Tree)
webpack和lint等许多的工具和库的核心都是AST,利用AST语法
树的概念可以对代码进行检查分析等操作。当然如果你对AST有充分
的理解后也可以编写自己的类似工具。
复制代码
这些工具的原理都是通过Javascript Parser把代码转化为一颗抽象语法树,它定义了代码的结构,通过操纵这颗树,我们可以精确定位到生命语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作
在计算机科学中,abstract syntax tree 是源代码(编程语言的源代码)的抽象语法结构的一种树的表现形式。
javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化成AST语法树来更适合程序分析,浏览器编译器一般会把源码转化成AST来进行分析和其他操作。
Abstract Syntax Tree 的用途
-
可以进行代码语法、风格的检查;代码的格式化、高亮、错误提示、自动补全,如JSLint/JSHint对代码错误或风格的检查。
-
代码混淆压缩
UglifyJS2等
- 优化变更代码,改变代码结构
- 代码打包工具webpack、rollup等等
- CommonJS、AMD、CMD、UMD等代码规范之间的转化
- CoffeeScript、TypeScript、JSX等转化成javascript
如何使用ast
使用esprima、escodegen、estraverse实现 ast操作
在没有babel之前我们是这样做的
- 解析我们的javascirpt语法 》 ast
esprima: esprima.org/demo/parse.…
- 遍历树(先序深度优先遍历) 》 更改树结构
estraverse:
- 生成新的内容
escodegen
npm install esprima estraverse escodegen
js语句
function ast() {}
复制代码
esprima编译后
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "ast"
},
"params": [],
"body": {
"type": "BlockStatement",
"body": []
},
"generator": false,
"expression": false,
"async": false
}
],
"sourceType": "script"
}
复制代码
esprima estraverse escodegen 实现基本步骤
let esprima = require("esprima");
let estraverse = require('estraverse');
let escodegen = require('escodegen');
let code = `function ast() {}`
let tree = esprima.parseScript(code);
estraverse.traverse(tree, {
enter(node) {
console.log('enter', node.type);
if(node.type === 'Identifier') {
node.name = 'tom';
}
}
})
let r = escodegen.generate(tree);
console.log(r);
//-----------------------------------
enter Program
enter FunctionDeclaration
enter Identifier
enter BlockStatement
function tom() {
}
复制代码
直接用babel实现操作语法树
实现babel-plugin-arrow-fucntion (箭头函数转换成es5)
箭头函数转成普通函数
- 箭头函数ast type > ArrowFunctionExpression
- 转ast: babel-core
- 改ast: babel-types
箭头函数的ast
let sum = (a, b) => { return a + b };
复制代码
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "sum"
},
"init": {
"type": "ArrowFunctionExpression",
"id": null,
"params": [
{
"type": "Identifier",
"name": "a"
},
{
"type": "Identifier",
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Identifier",
"name": "a"
},
"right": {
"type": "Identifier",
"name": "b"
}
}
}
]
},
"generator": false,
"expression": false,
"async": false
}
}
],
"kind": "let"
}
],
"sourceType": "script"
}
```
es5有名函数(返回值有代码块)的ast
```js
let babel = require('babel-core');
let t = require('babel-types');
let code = `let sum = (a, b) => { return a + b }`;
//.babelrc
let ArrowPlugin = {
vistor: {
//path: 树的类型
ArrowFunctionExpresstion(path) {
//通过路径匹配到的节点
let node = path.node;
let params = path.params;
let body = node.body;
//生成一个函数表达式
let funcs = t.functionExpression(null, params, body, false, false);
path.replaceWith(funcs);
}
}
}
let r = babel.transform(code, {
plugins: [
ArrowPlugin
]
})
console.log(r.code);
/*
codee 之前和之后的对比
之前: let sum = (a, b) => {return a + b};
之后: let sum = function(a, b) { return a + b};
*/
复制代码
接下来兼容生成返回值没有代码块的ast
let babel = require('bable-core');
let t = require('babel-types');
let code = `let sum = (a + b) => a + b`
let ArrowPlugin = {
vistor: {
//找到树的类型
ArrowFunctionExpression(path) {
let node = path.node;
let params = path.params;
let body = node.body;
//生成一个函数表达式
//判断是否body是否有代码块
if(!t.isBlockStatement(body)){
//如果不是代码块,也就是上面的情况
let returnStatement = t.returnStatement(body);
body = t.blockStatement([returnStatement]);
}
let func = t.functionExpression(null, params, body, false, false)
path.replaceWith(funcs);
}
}
}
let r = babel.transform(code, {
plugins: [ArrowPlugin]
})
/*
let sum = function() {
return a + b;
}
*/
复制代码
实现plugin es6 > es5
- es5
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
复制代码
- es5 asts off
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Person"
},
"params": [
{
"type": "Identifier",
"name": "name"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "name"
}
},
"right": {
"type": "Identifier",
"name": "name"
}
}
}
]
},
"generator": false,
"expression": false,
"async": false
},
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Identifier",
"name": "Person"
},
"property": {
"type": "Identifier",
"name": "prototype"
}
},
"property": {
"type": "Identifier",
"name": "getName"
}
},
"right": {
"type": "FunctionExpression",
"id": null,
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "name"
}
}
}
]
},
"generator": false,
"expression": false,
"async": false
}
}
}
],
"sourceType": "script"
}
复制代码
- es6
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
复制代码
- es6 ast
{
"type": "Program",
"body": [
{
"type": "ClassDeclaration",
"id": {
"type": "Identifier",
"name": "Person"
},
"superClass": null,
"body": {
"type": "ClassBody",
"body": [
{
"type": "MethodDefinition",
"key": {
"type": "Identifier",
"name": "constructor"
},
"computed": false,
"value": {
"type": "FunctionExpression",
"id": null,
"params": [
{
"type": "Identifier",
"name": "name"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "name"
}
},
"right": {
"type": "Identifier",
"name": "name"
}
}
}
]
},
"generator": false,
"expression": false,
"async": false
},
"kind": "constructor",
"static": false
},
{
"type": "MethodDefinition",
"key": {
"type": "Identifier",
"name": "getName"
},
"computed": false,
"value": {
"type": "FunctionExpression",
"id": null,
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "ThisExpression"
},
"property": {
"type": "Identifier",
"name": "name"
}
}
}
]
},
"generator": false,
"expression": false,
"async": false
},
"kind": "method",
"static": false
}
]
}
}
],
"sourceType": "script"
}
复制代码
操作ast实现
let babel = require('babel-core');
let t = require('babel-types');
let classPlugin = {
vistor = {
ClassDeclaration(path) {
let node = path.node;
let className = path.id.name;
let classList = node.body.body;
//将className转成一个标识符
className = t.identifier(className);
//生成一个函数体
let funcs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
let es5Func = [];
classList.forEach((item, index) => {
//函数的代码体
let body = classList[index].body;
if(item.kind === 'constructor') {
//如果是构造函数那就生成新的函数将默认的空函数替换
let params = item.params.length ? item.params.map(item => item.name) : [];
params = t.identifier(params);
funcs = t.functionDeclaration(className, [params], body, false, false);
}else {
//原型上的方法
let protoObj = t.memberExpression(className, t.identifier("prototype"));
let left = t.merberExpression(protoObj, t.identifier(item.key.name));
let right = t.functionExpression(null, [], body, false, false);
let assign = t.assignmentExpression('=', left, right)
//多个原型上的方法
esFunc.push(assign);
}
});
if(esFunc.length === 0) {
path.replaceWith(funcs);
}else {
es5Func.push(funcs);
path.replaceWithMultiple(esFuncs);
}
}
}
}
babel.transform(code, {
plugins: [
classPlugin
]
})
/*
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
*/
复制代码
总结
好了,到此为止,如果你认真看到这里,想必你应该知道了,编写一个babel和plugin的原理了,其实就是操作ast语法树进行修改然后再渲染成新的ast语法树的过程,而这个过程中需要的思路和逻辑,就是对应的babel和plugin的原理了。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END