JavaScrpit AST實戰(zhàn)

前言

每個編程語言都有自己的AST增炭,了解AST并能進(jìn)行一些開發(fā)款筑,會給我們的項目開發(fā)提供很大的便利轰绵。下面就帶大家一探究竟

通過本文能了解到什么
  1. JS AST結(jié)構(gòu)和屬性
  2. babel插件開發(fā)

JS AST簡介

AST也就是抽象語法樹聪铺。簡單來說就是把程序用樹狀形式展現(xiàn)。
每種語言(HTML摄杂,CSS坝咐,JS等)都有自己的AST,而且還有多種AST解析器析恢。

回歸JS本身墨坚,常見的AST解析器有:

  • acorn
  • @babel/parser
  • Typescript
  • Uglify-js
  • 等等

不同解析器解析出來的AST有些許差異,但本質(zhì)上是一樣的映挂。
本文將基于@babel/parser來進(jìn)行示例和講解

下面來看一句常見的代碼

import ajax from 'axios'

轉(zhuǎn)換后的AST結(jié)構(gòu)如下:

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

內(nèi)容是不是比想象的多泽篮?莫慌,我們一點一點看柑船。
來一張簡略圖:


image.png
ImportDeclaration

語句的類型咪辱,表明是一個import的聲明。
常見的有:
- VariableDeclaration:var x = 'init'
- FunctionDeclaration:function func(){}
- ExportNamedDeclaration:export function exp(){}
- IfStatement:if(1>0){}
- WhileStatement:while(true){}
- ForStatement:for(;;){}
- 不一一列舉
既然是一個引入表達(dá)式椎组,自然分左右兩部分油狂,左邊的是specifiers,右邊的是source

specifiers

specifiers節(jié)點會有一個列表來保存specifier
如果左邊只聲明了一個變量寸癌,那么會給一個ImportDefaultSpecifier
如果左邊是多個聲明专筷,就會是一個ImportSpecifier列表
什么叫左邊有多個聲明?看下面的示例

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

變量的聲明要保持唯一性
而Identifier就是鼓搗這個事情的

source

source包含一個字符串節(jié)點StringLiteral蒸苇,對應(yīng)了引用資源所在位置磷蛹。示例中就是axios

AST是如何轉(zhuǎn)換出來的呢?

以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里執(zhí)行一下溪烤,就能打印出AST
通過這個小示例味咳,大家應(yīng)該對AST有個初步的了解,下面我們談?wù)劻私馑惺裁匆饬x

應(yīng)用場景以及實戰(zhàn)

實際上檬嘀,我們在項目中槽驶,AST技術(shù)隨處可見

  • Babel對es6語法的轉(zhuǎn)換
  • Webpack對依賴的收集
  • Uglify-js對代碼的壓縮
  • 組件庫的按需加載babel-plugin
  • 等等

為了更好的理解AST,我們定義一個場景鸳兽,然后實戰(zhàn)一下掂铐。
場景:把import轉(zhuǎn)換成require,類似于babel的轉(zhuǎn)換
目標(biāo):通過AST轉(zhuǎn)換,把語句

import ajax from 'axios'

轉(zhuǎn)為

var ajax = require('axios')

要達(dá)到這個效果揍异,首先我們要寫一個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)
    //創(chuàng)建新節(jié)點
    var newNode = t.variableDeclaration("var", [declarator])
    //節(jié)點替換
    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)

輸出結(jié)果:

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

目標(biāo)達(dá)成!

babel-plugin

在babel的官網(wǎng)有開發(fā)文檔衷掷,這里只是簡單的描述一下注意要點:

  • 插件要求返回一個visitor對象辱姨。
  • 可以攔截所有的節(jié)點,函數(shù)名稱就是節(jié)點類型戚嗅,入?yún)⑹莗ath雨涛,可以通過path.node來獲取當(dāng)前節(jié)點
  • @babel/types提供了大量節(jié)點操作的API枢舶,同樣可以在官網(wǎng)看的詳細(xì)的說明
transform

這里的代碼大家是不是看著很熟悉。沒錯镜悉,就是.babelrc里的配置祟辟。我們開發(fā)的插件,配置到.babelrc的plugins里侣肄,就可以全局運行了旧困。

寫在最后

JS的AST,給我們提供了實現(xiàn)各種可能得機會稼锅。我們可以自定義一個語法吼具,可以將組件的按需引入過程簡化等等。同時不僅僅是JS矩距,CSS拗盒,HTML,SQL都可以在ast語法級別去進(jìn)行一些有趣的操作锥债。該篇文章只是帶大家簡單入門陡蝇。寫在最后:前端不僅僅是UI,可玩的東西還有很多

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哮肚,一起剝皮案震驚了整個濱河市登夫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌允趟,老刑警劉巖恼策,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潮剪,居然都是意外死亡涣楷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門抗碰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狮斗,“玉大人,你說我怎么就攤上這事改含∏榱洌” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵捍壤,是天一觀的道長。 經(jīng)常有香客問我鞍爱,道長鹃觉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任睹逃,我火速辦了婚禮盗扇,結(jié)果婚禮上祷肯,老公的妹妹穿的比我還像新娘。我一直安慰自己疗隶,他們只是感情好佑笋,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斑鼻,像睡著了一般蒋纬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坚弱,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天蜀备,我揣著相機與錄音,去河邊找鬼荒叶。 笑死碾阁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的些楣。 我是一名探鬼主播脂凶,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼愁茁!你這毒婦竟也來了蚕钦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤埋市,失蹤者是張志新(化名)和其女友劉穎冠桃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體道宅,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡食听,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了污茵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片樱报。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泞当,靈堂內(nèi)的尸體忽然破棺而出迹蛤,到底是詐尸還是另有隱情,我是刑警寧澤襟士,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布盗飒,位于F島的核電站,受9級特大地震影響陋桂,放射性物質(zhì)發(fā)生泄漏逆趣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一嗜历、第九天 我趴在偏房一處隱蔽的房頂上張望宣渗。 院中可真熱鬧抖所,春花似錦、人聲如沸痕囱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞍恢。三九已至傻粘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間有序,已是汗流浹背抹腿。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旭寿,地道東北人警绩。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像盅称,于是被迫代替她去往敵國和親肩祥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354