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)