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"
        }
      }

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270