AST抽象語法樹和Babel插件

AST(Abstract Syntax Tree, AST)抽象語法樹,能夠把代碼轉譯成語法樹的表現形式

例以下面的代碼:node

var a = 3;
a + 5

AST抽象出來的樹結構:es6

image.png

Program表明的是根節點express

  • VariableDeclaration變量聲明npm

    • Identifier 標識符 + Numeric Literal數字字面量
  • BinaryExpression(二項式)babel

    • Identifier 標識符,operator 二項式運算符,Numeric Literal數字字面量

能夠到 astexplorer.net 查看AST的解析結果antd

編譯器過程

大多數編譯器的工做過程能夠分爲三部分:ide

  • Parse(解析)
  • Transform(轉換)
  • Generate(代碼生成)

image.png

安裝 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插件

來看下 babel是如何工做的, 首先經過npm安裝 @babel/corebabel-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)

參考連接

相關文章
相關標籤/搜索