1. 程式人生 > >快速寫一個babel插件

快速寫一個babel插件

ref 操作 如何快速 blob cor 文件 bst 調用 基礎

es6/7/8的出現,給我們帶來了很多方便,但是瀏覽器並不怎麽支持,目前chrome應該是支持率最高的,所以為了兼容我們只能把代碼轉成es5,這應該算是我們最初使用babel的一個緣由,隨著業務的開發,我們會有很多自己定制化的需求,單純的bebel並不能解決我們所有的問題,所以babel插件應用而來,本文將會采用較為通俗的語言來描述如何快速寫一個babel插件。

一、babel的作用

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

二、ast分析

那麽什麽是ast語法樹呢?
比如代碼var a = 12對應的就是下面的ast語法樹,是不是很懵?就這麽簡單的代碼弄出這麽多東西
技術分享圖片
先來個個人官方解釋,ast->Abstract Syntax Tree,也叫抽象語法樹,就是對代碼進行詞法分析之後再進行語法分析弄出來的東西,可以理解為代碼執行前的編譯過程,畢竟運行代碼的不是我們,所以要變成機器可是別的東東。
簡單分析下var a = 12這一句,首先我們知道這條語句是定義變量,定義了a,並且賦值12(非配內存什麽的這裏就不說了,跟理解ast沒啥用處),然後我們在對應看那個ast語法樹,開始的program就是根節點,不用管,然後是body,應該是到重點了,緊接著我們就看到了技術分享圖片

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

type

這裏我們把每個花括號叫做一個節點,每個節點代表了代碼中的一部分,變量名,然後是所賦的值,type表示了每一塊節點所表示的類型,那麽這些類型有那些呢,babel type這裏有詳細介紹,當然有很多種類型,這裏也無法給大家一一講解(我也不知道所有的),但是我們需要知道的只是我們要改變的代碼是什麽類型的節點,介紹個網址 AST explorer,在這個頁面中我們只需要把代碼寫進去,就會展示出代碼的ast語法樹,上面截圖就是來自於這個網站。

三、寫插件

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

插件格式

技術分享圖片
這是一個插件的基本格式,一個函數,參數是babel,這裏我們用到的是types這個屬性,所以只把它寫出來,然後就是返回一個對象,key是visiter,然後裏面又是個對象,但是key是我們熟悉的東西,就是一個babel-types類型,然後是一個箭頭函數,函數有兩個參數,path表示路徑,state表示狀態。
visitor字面意思就是訪問者,這裏也是這個意思,也就是我們要訪問哪個類型的節點,這裏是個CallExpression,字面意思就是調用表達式,類似於handle(),path參數表示當前節點的位置,包含的主要是當前節點(node)內容以及父節點(parent)內容,state先不管,有了這些我們就可以去修改代碼了。

一個簡單的插件

我們先來一個簡單的插件,要求是把所有定義變量名為a的換成是b
首先我們要找到定義變量的地方,然後判斷變量名是不是a,如果是就把它替換成b,思路就是這樣。開始動手,首先把這一句放到 AST explorer這個網站中,鼠標選中這一句代碼,右側就會展示出這句代碼轉成ast的樣子
技術分享圖片
我們看到這是一個變量定義的語句,所以我們要找的節點類型就是VariableDeclarator,所以寫成如下

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

然後我們要判斷他的變量名是不是a,可以從ast中看到VariableDeclarator的id屬性就是變量名部分,所以我們只要判斷id的name屬性是不是a就可以了

//訪問的是當前節點,所以操作對象是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,就是用這個來構建,替換的類型是個Identifier技術分享圖片所以我們也要構建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 explorer和babel type來快速構建,首先你要知道我們要構建的ast是什麽樣的,所以把代碼放到AST explorer中,我們就可以在右側看到它的ast樹,然後再根據節點類型,參考babel type的使用方法一層一層的構建。
下面舉個例子:
技術分享圖片
我們要構建聲明語句,第一層節點類型是VariableDeclaration,所以要寫這個類型的ast,看下babel-type中怎麽用
技術分享圖片
照著寫t.variableDeclaration(‘const‘, [declarators]),kind是const,後面是個數組,每一項是個VariableDeclarator。
我們再看ast,下一層就是個VariableDeclarator,還是去查下這個怎麽用技術分享圖片一個id,一個init,id就是變量名,init就是要賦的值,所以寫出來就是t.variableDeclarator(‘info‘, initExpression),是這樣嗎?要記住每一項都是ast,所以info也要構建成ast,看下ast樹中這個id是啥樣的,他是個identifier,然後使用方法是技術分享圖片
所以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
        )]))])

我們來驗證下
技術分享圖片
包括之前的把a換成b
技術分享圖片顯然是對的,插件中內容是
技術分享圖片

四、總結

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

快速寫一個babel插件