AST
(Abstract Syntax Tree, AST)形象语法树,能够把代码转译成语法树的表现形式
例如上面的代码:
var a = 3;
a + 5
AST
形象进去的树结构:
Program
代表的是根节点
-
VariableDeclaration
变量申明Identifier
标识符 +Numeric Literal
数字字面量
-
BinaryExpression
(二项式)Identifier
标识符,operator
二项式运算符,Numeric Literal
数字字面量
能够到 astexplorer.net 查看 AST
的解析后果
编译器过程
大多数编译器的工作过程能够分为三局部:
- Parse(解析)
- Transform(转换)
- Generate(代码生成)
装置 esprima
来了解编译的过程:
npm install esprima estraverse escodegen
const esprima = require('esprima')
const estraverse = require('estraverse')
const escodegen = require('escodegen')
let code = `var a = 3`
// Parse(解析)
let ast = esprima.parseScript(code);
//Transform(转换)
estraverse.traverse(ast, {enter(node) {console.log("enter",node.type);
},
leave(node) {console.log("leave",node.type);
}
});
// Generate(代码生成)
const result = escodegen.generate(ast);
Babel 对于 AST 的遍历是深度优先遍历,对于 AST 上的每一个分支 Babel 都会先向下遍历走到止境,而后再向上遍历退出刚遍历过的节点,而后寻找下一个分支。
AST
对语法树的遍历是 深度优先遍历
,所以会先向下遍历走到止境,而后再向上遍历退出刚遍历过的节点,寻找下一个分支,所以遍历的过程中控制台会打印上面的信息:
enter Program
enter VariableDeclaration
enter VariableDeclarator
enter Identifier
leave Identifier
enter Literal
leave Literal
leave VariableDeclarator
leave VariableDeclaration
leave Program
通过 type
的判断咱们能够批改变量的值:
estraverse.traverse(ast, {enter(node) {if(node.type === "Literal"){node.value = "change";}
}
});
// var a = "change";
babel 插件
来看下 babel
是如何工作的,首先通过 npm 装置 @babel/core
和 babel-types
:
npm install @babel/core
咱们晓得 babel
能编译 es6 代码,例如最根底的 const
和箭头函数:
// es2015 的 const 和 arrow function
const add = (a, b) => a + b;
// Babel 转译后
var add = function add(a, b) {return a + b;};
咱们能够到 astexplorer 查看生成的语法树:
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration", // 变量申明
"declarations": [ // 具体申明
{
"type": "VariableDeclarator", // 变量申明
"id": {
"type": "Identifier", // 标识符(最根底的)"name": "add" // 函数名
},
"init": {
"type": "ArrowFunctionExpression", // 箭头函数
"id": null,
"expression": true,
"generator": false,
"params": [ // 参数
{
"type": "Identifier",
"name": "a"
},
{
"type": "Identifier",
"name": "b"
}
],
"body": { // 函数体
"type": "BinaryExpression", // 二项式
"left": { // 二项式右边
"type": "Identifier",
"name": "a"
},
"operator": "+", // 二项式运算符
"right": { // 二项式左边
"type": "Identifier",
"name": "b"
}
}
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
通过代码模仿一下:
const babel = require('babel-core');
const t = require('babel-types');
let code = `let add = (a, b)=>{return a+b}`;
let ArrowPlugins = {
visitor: {ArrowFunctionExpression(path) {let { node} = path;
let body = node.body;
let params = node.params;
let r = t.functionExpression(null, params, body, false, false);
path.replaceWith(r);
}
}
}
let result = babel.transform(code, {
plugins: [ArrowPlugins]
})
console.log(result.code);
咱们能够在访问者 visitor
中捕捉到匹配的type
,在回调函数外面替换箭头函数。
class 转换
const babel = require("@babel/core");
const typs = require("@babel/types");
const code = `
class Animal {constructor(name){this.name = name}
getName(){return this.name}
}
`
const classPlugins = {
visitor:{ClassDeclaration(path){
let node = path.node;
let body = node.body.body;
let id = node.id;
let params = node.params;
let methods = body.map(method=>{if(method.kind === "constructor"){return typs.functionDeclaration(id, method.params, method.body)
}else{
// Animal.prototype
let left = typs.memberExpression(id,typs.identifier("prototype"));
// Animal.prototype.getName
left = typs.memberExpression(left,method.key);
let right = typs.functionExpression(null,method.params,method.body);
return typs.assignmentExpression("=",left,right);
}
})
path.replaceWithMultiple(methods);
}
}
}
const result = babel.transform(code, {plugins: [classPlugins]
})
console.log(result.code)
import 转换
const babel = require('@babel/core');
const types = require('@babel/types');
const code = `import antd,{Button} from "antd"`;
const importPlugin = {
visitor: {ImportDeclaration(path) {
let node = path.node
let specifiers = node.specifiers
if (
!(
specifiers.length == 1 &&
types.isImportDefaultSpecifier(specifiers[0])
)
) {specifiers = specifiers.map((specifier) => {let local = types.importDefaultSpecifier(specifier.local);
if (types.isImportDefaultSpecifier(specifier)) {return types.importDeclaration([local],types.stringLiteral(node.source.value))
} else {return types.importDeclaration([local],types.stringLiteral(node.source.value+"/lib/"+specifier.local.name))
}
});
path.replaceWithMultiple(specifiers)
}
},
},
}
const result = babel.transform(code, {plugins: [importPlugin],
});
console.log(result.code)
参考链接
- 看了就懂的 AST 和 Babel 工作流程
- Step-by-step guide for writing a custom babel transformation
- Understanding ASTs by Building Your Own Babel Plugin