快速寫一個babel插件

es6/7/8的出現,給咱們帶來了不少方便,可是瀏覽器並不怎麼支持,目前chrome應該是支持率最高的,因此爲了兼容咱們只能把代碼轉成es5,這應該算是咱們最初使用babel的一個原因,隨着業務的開發,咱們會有不少本身定製化的需求,單純的bebel並不能解決咱們全部的問題,因此babel插件應用而來,本文將會採用較爲通俗的語言來描述如何快速寫一個babel插件。node

1、babel的做用

babel的做用其實就是一個轉換器,把咱們的代碼轉成瀏覽器能夠運行的代碼,相似於加工廠的概念。解析代碼都是一個文件一個文件的處理,把代碼讀出來,而後通過處理,再輸出,在處理的過程當中每一個文件的代碼其實就是個大的字符串。可是咱們要把有些語法修改,好比let定義變量改爲var定義,很明顯用字符串替換是不現實的,這裏babel是把代碼轉成ast語法樹,而後通過一系列操做以後再轉成字符串輸出,git

2、ast分析

那麼什麼是ast語法樹呢?
好比代碼var a = 12對應的就是下面的ast語法樹,是否是很懵?就這麼簡單的代碼弄出這麼多東西
image.png
先來個我的官方解釋,ast->Abstract Syntax Tree,也叫抽象語法樹,就是對代碼進行詞法分析以後再進行語法分析弄出來的東西,能夠理解爲代碼執行前的編譯過程,畢竟運行代碼的不是咱們,因此要變成機器但是別的東東。
簡單分析下var a = 12這一句,首先咱們知道這條語句是定義變量,定義了a,而且賦值12(非配內存什麼的這裏就不說了,跟理解ast沒啥用處),而後咱們在對應看那個ast語法樹,開始的program就是根節點,不用管,而後是body,應該是到重點了,緊接着咱們就看到了image.pnges6

這句VariableDeclaration字面意思就是變量聲名,是否是跟咱們以前的分析對應上了,至於接下來爲啥又有個declarations數組,也很好理解,咱們聲明變量是否是能夠用逗號隔開同時寫多個,就像var a = 12, b = 16;,而他們在同一條聲明語句中,因此就用數組來表示了。再看具體的一個
image.png
一個id,一個init,也很直觀的看出,id就是咱們的變量名,init就是咱們的值,而後咱們看到有三個key在每一個花括號中都是一直在重複出現,就是type/start/end,type就是類型,start是代碼起始位置,end是結束位置,關於type這裏多介紹點。github

type

這裏咱們把每一個花括號叫作一個節點,每一個節點表明了代碼中的一部分,變量名,而後是所賦的值,type表示了每一塊節點所表示的類型,那麼這些類型有那些呢,babel type這裏有詳細介紹,固然有不少種類型,這裏也沒法給你們一一講解(我也不知道全部的),可是咱們須要知道的只是咱們要改變的代碼是什麼類型的節點,介紹個網址 AST explorer,在這個頁面中咱們只須要把代碼寫進去,就會展現出代碼的ast語法樹,上面截圖就是來自於這個網站。chrome

3、寫插件

基礎的東西講了寫,下面說下具體如何寫插件。數組

插件格式

image.png
這是一個插件的基本格式,一個函數,參數是babel,這裏咱們用到的是types這個屬性,因此只把它寫出來,而後就是返回一個對象,key是visiter,而後裏面又是個對象,可是key是咱們熟悉的東西,就是一個babel-types類型,而後是一個箭頭函數,函數有兩個參數,path表示路徑,state表示狀態。
visitor字面意思就是訪問者,這裏也是這個意思,也就是咱們要訪問哪一個類型的節點,這裏是個CallExpression,字面意思就是調用表達式,相似於handle(),path參數表示當前節點的位置,包含的主要是當前節點(node)內容以及父節點(parent)內容,state先無論,有了這些咱們就能夠去修改代碼了。瀏覽器

一個簡單的插件

咱們先來一個簡單的插件,要求是把全部定義變量名爲a的換成是b
首先咱們要找到定義變量的地方,而後判斷變量名是否是a,若是是就把它替換成b,思路就是這樣。開始動手,首先把這一句放到 AST explorer這個網站中,鼠標選中這一句代碼,右側就會展現出這句代碼轉成ast的樣子
image.png
咱們看到這是一個變量定義的語句,因此咱們要找的節點類型就是VariableDeclarator,因此寫成以下babel

visitor: {
    VariableDeclarator: (path, state) => {
         //code
    }
}

而後咱們要判斷他的變量名是否是a,能夠從ast中看到VariableDeclarator的id屬性就是變量名部分,因此咱們只要判斷id的name屬性是否是a就能夠了ide

//訪問的是當前節點,因此操做對象是path.node
if(path.node.id.name === 'a'){
    //code
}

有人可能會想這裏是否是直接path.node.id.name = 'b'就能夠了,若是你是操做object,那你就對了,不過這裏是ast語法樹,因此想改變某個值,就是用對應的ast來替換,因此咱們要把id是a的ast換成b的ast,那麼b的ast怎麼建立呢?很簡單,最外層的函數參數咱們引入了types,就是用這個來構建,替換的類型是個Identifierimage.png因此咱們也要構建b的Identifier,寫起來就是t.Identifier('b'),因此咱們的插件最終就是:函數

module.exports = function(babel){
    let t = babel.types;
    return {
        visitor: {
            VariableDeclarator(path, state) {
                if(path.node.id.name == 'a'){
                    path.node.id = t.Identifier('b')
                }
            }
            }
      }
}

因此咱們寫插件的時候只須要如下幾個步驟就能夠完成:
1.確認咱們要修改的節點類型(把代碼複製到ast explorer 中,一一對應)
2.找到修改的屬性是哪一個(這裏咱們修改id屬性)
3.根據舊的ast構建新的ast語句並替換(把b構建成ast語句替換原來的屬性)

構建ast

這裏的一個難點就是如何構建ast,代碼有不少種類型,咱們要修改就須要構建各類各樣類型的ast,這裏咱們結合AST explorerbabel type來快速構建,首先你要知道咱們要構建的ast是什麼樣的,因此把代碼放到AST explorer中,咱們就能夠在右側看到它的ast樹,而後再根據節點類型,參考babel type的使用方法一層一層的構建。
下面舉個例子:
image.png
咱們要構建聲明語句,第一層節點類型是VariableDeclaration,因此要寫這個類型的ast,看下babel-type中怎麼用
image.png
照着寫t.variableDeclaration('const', [declarators]),kind是const,後面是個數組,每一項是個VariableDeclarator。
咱們再看ast,下一層就是個VariableDeclarator,仍是去查下這個怎麼用image.png一個id,一個init,id就是變量名,init就是要賦的值,因此寫出來就是t.variableDeclarator('info', initExpression),是這樣嗎?要記住每一項都是ast,因此info也要構建成ast,看下ast樹中這個id是啥樣的,他是個identifier,而後使用方法是image.png
因此id就是t.identifier('info'),init表達式有點複雜,是個對象,因此繼續上面的步驟,寫出來以下

t.objectExpression([t.objectProperty(
            t.identifier('name'),
            t.stringLiteral('Steven'),
            false,
            false,
            null
        )])

連起來就是

t.variableDeclaration('let', [t.variableDeclarator(t.identifier('info'), t.objectExpression([t.objectProperty(
            t.identifier('name'),
            t.stringLiteral('Steven'),
            false,
            false,
            null
        )]))])

咱們來驗證下
image.png
包括以前的把a換成b
image.png顯然是對的,插件中內容是
image.png

4、總結

寫插件最快的方法就是,對照上面推薦的兩個網站一層一層的構建ast,而後作想要的操做。在GitHub上有全面的介紹寫插件的各個屬性及方法,看這裏教程

相關文章
相關標籤/搜索