前端开发之JavaScrpit AST 实战分析

在前端培训学习中每个编程语言都有自己的AST,了解AST并能进行一些开发,会给我们的项目开发提供很大的便利。下面就带大家一探究竟:
通过本文能了解到什么

  1. JS AST结构和属性
  2. babel插件开发
JS AST简介
AST也就是抽象语法树。简单来说就是把程序用树状形式展现。
每种语言(HTML,CSS,JS等)都有自己的AST,而且还有多种AST解析器。
回归JS本身,常见的AST解析器有:
? acorn
? @babel/parser
? Typescript
? Uglify-js
? 等等
不同解析器解析出来的AST有些许差异,但本质上是一样的。
本文将基于@babel/parser来进行示例和讲解
下面来看一句常见的代码
import ajax from 'axios'转换后的AST结构如下:
{
"type": "ImportDeclaration", "start": 0, "end": 24, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 24 } }, "specifiers": [ { "type": "ImportDefaultSpecifier", "start": 7, "end": 11, "loc": { "start": { "line": 1, "column": 7 }, "end": { "line": 1, "column": 11 } }, "local": { "type": "Identifier", "start": 7, "end": 11, "loc": { "start": { "line": 1, "column": 7 }, "end": { "line": 1, "column": 11 }, "identifierName": "ajax" }, "name": "ajax" } } ], "importKind": "value", "source": { "type": "StringLiteral", "start": 17, "end": 24, "loc": { "start": { "line": 1, "column": 17 }, "end": { "line": 1, "column": 24 } }, "extra": { "rawValue": "axios", "raw": "'axios'" }, "value": "axios" } }内容是不是比想象的多?莫慌,我们一点一点看。

来一张简略图:
前端开发之JavaScrpit AST 实战分析
文章图片

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语法的转换
? Webpack对依赖的收集
? Uglify-js对代码的压缩
? 组件库的按需加载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) {
【前端开发之JavaScrpit AST 实战分析】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里,就可以全局运行了。
作者:cd2001cjm

    推荐阅读