乐趣区

关于webpack:AST抽象语法树和Babel插件

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/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)

参考链接

  • 看了就懂的 AST 和 Babel 工作流程
  • Step-by-step guide for writing a custom babel transformation
  • Understanding ASTs by Building Your Own Babel Plugin
退出移动版