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 Programenter VariableDeclarationenter VariableDeclaratorenter Identifierleave Identifierenter Literalleave Literalleave VariableDeclaratorleave VariableDeclarationleave Program

通过type的判断咱们能够批改变量的值:

estraverse.traverse(ast, {  enter(node) {    if(node.type === "Literal"){      node.value = "change";    }  }});// var a = "change";

babel插件

来看下 babel是如何工作的, 首先通过npm装置 @babel/corebabel-types:

npm install @babel/core

咱们晓得babel能编译es6代码,例如最根底的const和箭头函数:

// es2015 的 const 和 arrow functionconst 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