53

JavaScrpit AST实战

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI1NDc0ODMzNg%3D%3D&%3Bmid=2247484801&%3Bidx=1&%3Bsn=e8b605e09bceae68dce89e67da50eea0
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

每个编程语言都有自己的AST,了解AST并能进行一些开发,会给我们的项目开发提供很大的便利。下面就带大家一探究竟

通过本文能了解到什么

1. JS AST结构和属性 2. babel插件开发

JS AST简介

AST也就是抽象语法树。简单来说就是把程序用树状形式展现。每种语言(HTML,CSS,JS等)都有自己的AST,而且还有多种AST解析器。

回归JS本身,常见的AST解析器有:

acorn @babel/parser Typescript Uglify-js 等等

为什么会有多种解析器?

JS AST并没有一个统一的规范。各家AST解析器都是为内部提供服务。所以有各自的解析器也就不难理解了。虽然不同解析器解析出来的AST在结构上有些许差异,但本质上类似。 本文将基于@babel/parser来进行示例和讲解

下面来看一句常见的代码

import ajax from 'axios'

转换后的AST结构如下:

{

"type": "ImportDeclaration",

"start": 0,

"end": 21,

"loc": {

"start": {

"line": 1,

"column": 0

},

"end": {

"line": 1,

"column": 21

}

},

"specifiers": [

{

"type": "ImportDefaultSpecifier",

"start": 7,

"end": 10,

"loc": {

"start": {

"line": 1,

"column": 7

},

"end": {

"line": 1,

"column": 10

}

},

"local": {

"type": "Identifier",

"start": 7,

"end": 10,

"loc": {

"start": {

"line": 1,

"column": 7

},

"end": {

"line": 1,

"column": 10

},

"identifierName": "Vue"

},

"name": "Vue"

}

}

],

"importKind": "value",

"source": {

"type": "StringLiteral",

"start": 16,

"end": 21,

"loc": {

"start": {

"line": 1,

"column": 16

},

"end": {

"line": 1,

"column": 21

}

},

"extra": {

"rawValue": "vue",

"raw": "'vue'"

},

"value": "vue"

}

}


内容是不是比想象的多?莫慌,我们一点一点看。来一张简略图: UzmaamE.png!web

ImportDeclaration

语句的类型,表明是一个import的声明。常见的有:

  • VariableDeclaration:var x = 'init'

  • FunctionDeclaration:function func(){}

  • ExportNamedDeclaration:export function exp(){}

  • IfStatement:if(1>0){}

  • WhileStatement:while(true){}

  • ForStatement:for(;;){}

  • 不一一列举

既然是一个引入表达式,自然分左右两部分,左边的是specifiers,右边的是source

specifiers

specifiers节点会有一个列表来保存specifier

  • 如果左边只声明了一个变量,那么会给一个ImportDefaultSpecifier

  • 如果左边是多个声明,就会是一个ImportSpecifier列表。

什么叫左边有多个声明?看下面的示例

import {a,b,c} from 'X' 

变量的声明要保持唯一性 而Identifier就是鼓捣这个事情的

source

source包含一个字符串节点StringLiteral,对应了引用资源所在位置。示例中就是axios

AST是如何转换出来的呢?

以babel为例子:

const parser = require('@babel/parser')

let codeString = `

import ajax from 'axios'

`;


let file = parser.parse(codeString,{

sourceType: "module"

})

console.dir(file.program.body)

在node里执行一下,就能打印出AST 通过这个小示例,大家应该对AST有个初步的了解,下面我们谈谈了解它有什么意义

应用场景以及实战

实际上,我们在项目中,AST技术随处可见

  • Babel对es6语法的转换

    让我们可以提前使用浏览器不支持的语法。babel通过AST将语法进行转换

  • Webpack对依赖的收集

    webpack在编译时,会从入口开始收集所有的依赖。而对依赖的判定也是基于AST的

  • 组件库的按需加载babel-plugin

    典型的就是各个组件库的按需加载,比如element-ui的按需加载babel-plugin

  • 等等

为了更好的理解AST,我们定义一个场景,然后实战一下。

场景:把import转换成require,类似于babel的转换

目标:通过AST转换,把语句

import ajax from 'axios'

转为

var ajax = require('axios')

要达到这个效果,首先我们要写一个babel-plugin。先上代码 babelPlugin.js代码如下:

const t = require('@babel/types');


module.exports = function babelPlugin(babel) {



function RequireTranslator(path){


var node = path.node

var specifiers = node.specifiers


//获取变量名称

var varName = specifiers[0].local.name;

//获取资源地址

var source = t.StringLiteral(path.node.source.value)

var local = t.identifier(varName)

var callee = t.identifier('require')

var varExpression = t.callExpression(callee,[source])

var declarator = t.variableDeclarator(local, varExpression)

//创建新节点

var newNode = t.variableDeclaration("var", [declarator])

//节点替换

path.replaceWith(newNode)


}


return {


visitor: {

ImportDeclaration(path) {

RequireTranslator.call(this,path)

}


}

};

};


测试代码:

const babel = require('@babel/core');

const babelPlugin = require('./babelPlugin')


let codeString = `

import ajax from 'axios'

`;


const plugins = [babelPlugin]


const {code} = babel.transform(codeString,{plugins:plugins});


console.dir(code)

输出结果:

'var ajax = require("axios");'

目标达成!

babel-plugin 

在babel的官网有开发文档,这里只是简单的描述一下注意要点:

插件要求返回一个visitor对象。 可以拦截所有的节点,函数名称就是节点类型,入参是path,可以通过path.node来获取当前节点 @babel/types提供了大量节点操作的API,同样可以在官网看的详细的说明 transform  这里的代码大家是不是看着很熟悉。 没错,就是.babelrc里的配置。我们开发的插件,配置到.babelrc的plugins里,就可以全局运行了。

写在最后

JS的AST,给我们提供了实现各种可能的机会。我们可以自定义一个语法,可以将组件的按需引入过程简化等等。同时不仅仅是JS,CSS,HTML,SQL等语言都可以在ast语法级别去进行一些有趣的操作。该篇文章只是带大家简单入门。

前端不仅仅是UI,可玩的东西还有很多


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK