js中的AST

来源:http://www.chinese-glasses.com 作者:Web前端 人气:152 发布时间:2020-04-15
摘要:抽象语法树能做什么? export default function(babel) { // plugin contents} cd到一个你喜欢的目录,通过npm -y init初始化操作后,通过npm i @babel/corebabel-types -D安装依赖 我们写一个简单的插件,把所

抽象语法树能做什么?

export default function(babel) { // plugin contents}

cd到一个你喜欢的目录,通过npm -y init初始化操作后,通过npm i @babel/core babel-types -D安装依赖

我们写一个简单的插件,把所有定义变量名为a的换成b, 先从astexplorer看下var a = 1的 AST

这里推荐一下astexplorer AST的可视化工具,astexplorer,可以直接进行对代码进行AST转换~

我们介绍了babel的功能、babel工作的三个阶段以及怎么写一个简单的插件,写插件主要用到了transform这个函数,插件的的函数返回一个对象,visitor是这个插件的访问者,通过它来访问AST,在visitor里定义一些函数做相关的操作。如果想了解更多有关babel插件的知识可访问babel插件手册

有了这棵树,我们就可以通过操纵这颗树,精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作。

我们发现path除了含有节点之间的关系之外,同时它还包含关于该路径的其他元数据:

源代码的AST结构如下:

{ "parent": {...}, "node": {...}, "hub": {...}, "contexts": [], "data": {}, "shouldSkip": false, "shouldStop": false, "removed": false, "state": null, "opts": null, "skipKeys": null, "parentPath": null, "context": null, "container": null, "listKey": null, "inList": false, "parentKey": null, "key": null, "scope": null, "type": null, "typeAnnotation": null}

上面我们已经得到了我们分词的结果,需要将词汇进行一个立体的组合,确定词语之间的关系,确定词语最终的表达含义。

export default function({ types: t }) { return { visitor: { // visitor contents } };};
const sum=(a,b)=a+b;

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成比较简单,就是深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

10bet,这就是我们所说的分词,也是最小单元,因为如果我们把它再拆分出去的话,那就没有什么实际意义了。

export default function({ types: t }) { return { visitor: { Identifier(path, state) {}, ASTNodeTypeHere(path, state) {} } };};

目标代码的AST结构如下:

有了以上的基础知识之后我们看看babel插件是怎么编写的。

聊到AST的用途,其应用非常广泛,下面我简单罗列了一些:

Visitor 中的每个函数接收2个参数:path和state;

至此,我们的函数转换完成,达到预期效果。

先从一个接收了当前babel对象作为参数的function开始。

趁热打铁

解析步骤接收代码并输出 AST

如何生成AST?

我们经常会这样写:

通过分词,我们可以得到如下结果:

Babel 的三个主要处理步骤分别是:解析(parse)转换(transform)生成(generate)

“2019年是祖国70周年”

将子节点Identifier表示为一个路径(Path)的话,看起来是这样的:

// 目标代码的 `AST`{ "type": "Program", "start": 0, "end": 48, "body": [ { "type": "VariableDeclaration", "start": 0, "end": 48, "declarations": [ { "type": "VariableDeclarator", "start": 6, "end": 47, "id": { "type": "Identifier", "start": 6, "end": 9, "name": "sum" }, "init": { "type": "FunctionExpression", "start": 12, "end": 47, "id": null, "expression": false, "generator": false, "async": false, "params": [ { "type": "Identifier", "start": 22, "end": 23, "name": "a" }, { "type": "Identifier", "start": 25, "end": 26, "name": "b" } ], "body": { "type": "BlockStatement", "start": 28, "end": 47, "body": [ { "type": "ReturnStatement", "start": 32, "end": 45, "argument": { "type": "BinaryExpression", "start": 39, "end": 44, "left": { "type": "Identifier", "start": 39, "end": 40, "name": "a" }, "operator": "+", "right": { "type": "Identifier", "start": 43, "end": 44, "name": "b" } } } ] } } } ], "kind": "const" } ], "sourceType": "module"}

2、写一个简单的插件

完成代码如下:

时间: 2019-09-24阅读: 125标签: 插件

执行代码,打印结果如下:

import * as babel from '@babel/core';const c = `var a = 1`;const { code } = babel.transform(c, { plugins: [ function({ types: t }) { return { visitor: { VariableDeclarator(path) { if (path.node.id.name == 'a') { path.node.id = t.identifier('b') } } } } } ]})console.log(code); // var b =

AST在日常业务中也许很难涉及到,有可能你还没有听过,但其实很多时候你已经在使用它了,只是没有太多关注而已,现在流行的webpack,eslint等很多插件或者包都有涉及~

export default function({ types: t }) { //}

本文也只是通过对AST的一些介绍,起一个抛砖引玉的作用,让你对它 有一个初步的认识,对它不在感觉那么陌生。

例如我们要实现把import { Button } from 'antd'转成import Button from 'antd/lib/button'

我们关心的是body里面的内容,通过对比,我们发现主要不同就在于init这一段,一个是ArrowFunctionExpression, 另一个是FunctionExpression, 我们只需要将ArrowFunctionExpression下的内容改造成跟FunctionExpression即可。

{ type: "FunctionDeclaration", id: { type: "Identifier", name: "square" }, ...}

首先,我们来看一个简单的例子,我们如何将es6中的箭头函数转换成es5中的普通函数,即:

三、babel的处理步骤

var a = 1;

我们要把id属性是 a 的替换成 b 就好了。但是这里不能直接path.node.id.name = 'b'。如果操作的是object,就没问题,但是这里是 AST 语法树,所以想改变某个值,就是用对应的 AST 来替换,现在我们用新的标识符来替换这个属性。

接下来我们看看如何实现?

1、插件格式

// 目标代码function Person(name) { this.name = name;}Person.prototype.getName = function () { return this.name;};

Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。在这个源码到源码的转换过程当中,抽象语法树,即AST,起到了重要的作用。

听起来还是很绕,没关系,你可以简单理解为它就是你所写代码的的树状结构化表现形式

一、babel介绍

let visitor = { ArrowFunctionExpression(path){ }}
export default function({ types: t }) { return { visitor: { VariableDeclarator(path, state) { if (path.node.id.name == 'a') { path.node.id = t.identifier('b') } } } }}

ok~ 接下来我们来一起见证奇迹。

转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程,同时也是插件将要介入工作的部分。在babel-loader中有两种方式可以配置babel插件,我们经常会配置:

在了解如何生成AST之前,有必要了解一下Parser(常见的Parser有esprima、traceur、acorn、shift等)

注意:plugins 的插件使用顺序是顺序的,而 preset 则是逆序的。所以上面的执行方式是extracttransform-runtimestage-0env

如何将es6中的class改造成es5的function形式~

五、总结

最后通过path.replaceWith(func);将其替换即可;

{ "parent": { "type": "FunctionDeclaration", "id": {...}, .... }, "node": { "type": "Identifier", "name": "square" }}

上面画了很大篇幅聊了聊AST是什么以及它是如何生成的,说到底,还是不知道AST这玩意有啥用,到底怎么使用。。

抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,一种编程语言的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。和我们的常说的虚拟DOM有点像,虚拟dom是用json的格式把DOM结构抽象成js对象,用于描述DOM的结构,每个节点的类型、属性等等,类似的AST是把js的源代码抽象为js对象的格式,以方便描述这段代码的语法。程序代码本身被映射成为一棵语法树,通过操纵语法树,我们能够精准的获得程序代码中的每一个精确的节点。例如声明语句,赋值语句等。

上面刚刚认识了如何使用AST进行代码改造,不妨趁热打铁,再来试试下面这个问题。

rules: [ { test: /.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ["env", 'stage-0'], plugins: [ ["extract", { "library": "lodash" }], ["transform-runtime", {}] ] }, }]

目标分析

接着返回一个对象,其visitor属性是这个插件的主要访问者。

其中里面的start和end我们不用在意,其只是为了标记其所在代码的位置。

四、编写一个简单的babel插件

我们可以把这句话拆分成最小单元,即:2019年、是、祖国、70、周年。

通过对比 AST 发现,specifiers里的type和source不同。

一种是单一处理,结构如下,其中path代表当前遍历的路径path.node代表当前变量的节点

从上面可以看出我们要找的节点类型是VariableDeclarator,以下是我们写出的插件代码:

什么是语法分析?

Path是表示两个节点之间连接的对象。表示AST中节点之间的相互关联关系。例如,如果有下面这样一个节点及其子节点︰

简单来说语法分析是对语句和表达式识别,确定之前的关系,这是个递归过程。

// import { Button } from 'antd'"specifiers": [ { "type": "ImportSpecifier", ... }],"source": { "type": "Literal", "start": 23, "end": 29, "value": "antd", "raw": "'antd'"}

// import Button from 'antd/lib/button'"specifiers": [ { "type": "ImportDefaultSpecifier", ... }],"source": { "type": "Literal", "start": 19, "end": 36, "value": "antd/lib/button", "raw": "'antd/lib/button'"}

import * as babel from '@babel/core';const c = `import { Button } from 'antd'`;const { code } = babel.transform(c, { plugins: [ function({ types: t }) { return { visitor: { ImportDeclaration(path) { const { node: { specifiers, source } } = path; if (!t.isImportDefaultSpecifier(specifiers[0])) { // 对 specifiers 进行判断 const newImport = specifiers.map(specifier = ( t.importDeclaration( [t.ImportDefaultSpecifier(specifier.local)], t.stringLiteral(`${source.value}/lib/${specifier.local.name}`) ) )) path.replaceWithMultiple(newImport) } } } } } ]})console.log(code); // import Button from "antd/lib/Button"

这里我们只需要处理一次,所以采用第一种方式。

{ "type": "Program", "start": 0, "end": 10, "body": [ { "type": "VariableDeclaration", "start": 0, "end": 9, "declarations": [ { "type": "VariableDeclarator", "start": 4, "end": 9, "id": { "type": "Identifier", "start": 4, "end": 5, "name": "a" }, "init": { "type": "Literal", "start": 8, "end": 9, "value": 1, "raw": "1" } } ], "kind": "var" } ], "sourceType": "module"}
let visitor = { ArrowFunctionExpression : { enter(node){ }, leave(node){ } }}

3、实现一个简单的按需打包的功能

语法单元是被解析语法当中具备实际意义的最小单元,简单的来理解就是自然语言中的词语。

二、抽象语法树(AST)

这里我们需要用到babel中的transform方法,它可以将js代码转换成AST,过程中可以通过使用plugins对AST进行改造,最终生成新的AST和js代码,其整个过程用网上一个比较贴切的图就是:

我们用@babel/core中的transform方法对我们写的插件进行测试一下:

ClassDeclaration (path) { let node = path.node; //当前节点 let id = node.id; //节点id let methods = node.body.body; // 方法数组 let constructorFunction = null; // 构造方法 let functions = []; // 目标方法 methods.forEach(method = { //如果是构造方法 if ( method.kind === 'constructor' ) { constructorFunction = types.functionDeclaration(id, method.params, method.body, false, false); functions.push(constructorFunction) } else { //普通方法 let memberExpression = types.memberExpression(types.memberExpression(id, types.identifier('prototype'), false), method.key, false); let functionExpression = types.functionExpression(null, method.params, method.body, false, false); let assignmentExpression = types.assignmentExpression('=', memberExpression, functionExpression); functions.push(types.expressionStatement(assignmentExpression)); } }) //判断,replaceWithMultiple用于多重替换 if(functions.length === 1){ path.replaceWith(functions[0]) }else{ path.replaceWithMultiple(functions) }}

如果说你不了解AST的话,这无疑是一个很困难的问题,根本无从下手,如果你了解AST的话,这将是一个非常easy的例子。

const sum = function(a,b){ return a+b;}

需要操作AST代码,这里,我们需要借助两个库,分别是@babel/core和babel-types。

怎么样,有没有很神奇!!

本文由10bet发布于Web前端,转载请注明出处:js中的AST

关键词:

上一篇:网站导航的设计细节10bet

下一篇:没有了

频道精选

最火资讯