html转换成抽象语法树,AST抽象语法树和Babel插件

  • Post author:
  • Post category:其他


AST(Abstract Syntax Tree, AST)抽象语法树,可以把代码转译成语法树的表现形式

例如下面的代码:var a = 3;

a + 5

AST抽象出来的树结构:

637d5b99e95e46848bbc0e7948782b63.png

Program代表的是根节点VariableDeclaration变量声明Identifier 标识符 + Numeric Literal数字字面量

BinaryExpression(二项式)Identifier 标识符,operator 二项式运算符,Numeric Literal数字字面量

可以到 astexplorer.net 查看AST的解析结果

编译器过程

大多数编译器的工作过程可以分为三部分:Parse(解析)

Transform(转换)

Generate(代码生成)

5ad944b2c5bf1a1da08e17144927924d.png

安装 esprima 来理解编译的过程:npm install esprima estraverse escodegenconst 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/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,在回调函数里面替换箭头函数。

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

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[AST抽象语法树和Babel插件]http://www.zyiz.net/tech/detail-148723.html