JavaScrpit AST 实战

前端瓶子君

共 4658字,需浏览 10分钟

 ·

2021-03-25 15:10

关注 前端瓶子君,回复“交流

加入我们一起学习,天天进步

作者:cd2001cjm(本文来自作者投稿)

https://www.jianshu.com/p/8bbc8f43a2ae

前言


每个编程语言都有自己的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"        }      }


内容是不是比想象的多?莫慌,我们一点一点看。


来一张简略图:



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

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,可玩的东西还有很多

最后

欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿
回复「算法」,加入前端算法源码编程群,每日一刷(工作日),每题瓶子君都会很认真的解答哟
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发”就是最大的支持
浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报