理解 Babel 外掛

發表於2016-09-30

tb1cvgdnpxxxxb2xfxxxxxxxxxx-900-500

前言

相信目前常與 ES6 程式碼打交道的同學對 Babel 應該不會陌生,在 ES6 程式碼被編譯轉化為 ES5 程式碼的過程中,Babel 外掛顯得尤為重要,我們最後經由 Babel 生成的程式碼取決於外掛在這一層中做了什麼事,在探索這其中的過程之前,我們先來了解下一些所需的基礎知識。

抽象語法樹

Babel 的工作流可以用下面一張圖來表示,程式碼首先經由 babylon 解析成抽象語法樹(AST),後經一些遍歷和分析轉換(主要過程),最後根據轉換後的 AST 生成新的常規程式碼。

tb1np2onpxxxxb_xpxxxxxxxxxx-1958-812

在這其中,理解清楚 AST 十分重要,我們之所以需要將程式碼轉換為 AST 也是為了讓計算機能夠更好地進行理解。我們可以來看看下面這段程式碼被解析成 AST 後對應的結構圖:

tb1nndqnpxxxxxmxfxxxxxxxxxx-1015-756

所有的 AST 根節點都是 Program 節點,從上圖中我們可以看到解析生成的 AST 的結構的各個 Node 節點都很細微,Babylon AST 有個文件對每個節點型別都做了詳細的說明,你可以對照各個節點型別在這查詢到所需要的資訊。在這個例子中,我們主要關注函式宣告裡的內容, IfStatement 對應程式碼中的 if...else 區塊的內容,我們先對條件(test)進行判斷,這裡是個簡單的二進位制表示式,我們的分支也會從這個條件繼續進行下去,consequent 代表條件值為 true 的分支,alternate 代表條件值為 false 的分支,最後兩條分支各自在 ReturnStatement 節點進行返回。

瞭解 AST 各個節點的型別是後續編寫外掛的關緊,AST 通常情況下都是比較複雜的,上述一段簡單的函式定義也生成了比較大的 AST,對於一些複雜的程式,我們可以藉助astexplorer 來幫我們分析 AST 的結構。

遍歷節點

在外掛裡進行節點遍歷需要先了解 visitor 和 path 的概念,前者相當於從眾多節點型別中選擇開發者所需要的節點,後者相當於對節點之間的關係的訪問。

visitor

Babel 使用 babel-traverse 進行樹狀的遍歷,對於 AST 樹上的每一個分支我們都會先向下遍歷走到盡頭,然後向上遍歷退出遍歷過的節點尋找下一個分支。Babel 提供我們一個visitor 物件供我們獲取 AST 裡所需的具體節點來進行訪問,比如我只想訪問 if...else 生成的節點,我們可以在 visitor 裡指定獲取它所對應的節點:

繼續上述所說的遍歷,其實這種遍歷會讓每個節點都會被訪問兩次,一次是向下遍歷代表進入(enter),一次是向上退出(exit)。因此實際上每個節點都會有 enterexit 方法,在實際操作的時候需要注意這種遍歷方式可能會引起的一些問題,上述例子是省略掉enter 的簡寫。

path

visitor 模式中我們對節點的訪問實際上是對節點路徑的訪問,在這個模式中我們一般把path 當作引數傳入節點選擇器中。path 表示兩個節點之間的連線,通過這個物件我們可以訪問到節點、父節點以及進行一系列跟節點操作相關的方法(類似 DOM 的操作)。

替換節點

具備了 AST 相關知識和了解 visitor、path 後,就可以編寫一個簡單的 Babel 外掛了。我們要把上述的 abs 函式換成原生支援的 Math.abs 來進行呼叫 。

首先我們先解析下 abs(-8) 的 AST 結構,直接從表示式語句(ExpressionStatement)開始:

我們可以看到表示式語句下面的 expression 主要是函式呼叫表示式(CallExpression),因此我們也需要建立一個函式呼叫表示式,此外,Math.abs 是一個二元操作表示式,屬於MemberExpression 型別。上述兩個 AST 節點我們可以藉助 babel-types 裡提供的一些方法幫我們快速建立。

最後我們需要對此次函式呼叫不符合的節點進行過濾,過濾掉名字不等於 abs 的函式呼叫,因為 Babel 在遍歷的過程是遞迴的,如果不過濾做限制的話,程式將會一直執行最終報呼叫棧超過閾值的錯誤。

RangeError: unknown: Maximum call stack size exceeded

最終程式碼如下:

上述例子使用了 transform api 直接解析轉換生成了新的程式碼,另外在單獨編寫 Babel 外掛的時候,暴露的引數裡一般都含有常用的 babel-types 物件供使用。

總結

通過編寫 Babel 外掛我們能對 AST 有一定的瞭解,另外,我認為現階段 Babel 外掛不僅僅止於對 ES6 程式碼的轉換上,npm 上有一系列的外掛覆蓋了許多適合的應用場景,後續具有一定的探索性。

Reference

相關文章