AST
(Abstract Syntax Tree, AST)抽象语法树,能够把代码转译成语法树的表现形式
例以下面的代码:node
var a = 3; a + 5
AST
抽象出来的树结构:es6
Program
表明的是根节点express
VariableDeclaration
变量声明npm
Identifier
标识符 + Numeric Literal
数字字面量BinaryExpression
(二项式)babel
Identifier
标识符,operator
二项式运算符,Numeric Literal
数字字面量能够到 astexplorer.net 查看AST
的解析结果antd
大多数编译器的工做过程能够分为三部分:ide
安装 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
对语法树的遍历是 深度优先遍历
,因此会先向下遍历走到尽头,而后再向上遍历退出刚遍历过的节点,寻找下一个分支,因此遍历的过程当中控制台会打印下面的信息:ui
enter Program enter VariableDeclaration enter VariableDeclarator enter Identifier leave Identifier enter Literal leave Literal leave VariableDeclarator leave VariableDeclaration leave Program
经过type
的判断咱们能够修改变量的值:this
estraverse.traverse(ast, { enter(node) { if(node.type === "Literal"){ node.value = "change"; } } }); // var a = "change";
来看下 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
,在回调函数里面替换箭头函数。
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)
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)