Babel 的原理
作者:HZFEStudio
來源:SegmentFault 思否社區
完整高頻題庫倉庫地址:
https://github.com/hzfe/awesome-interview
完整高頻題庫閱讀地址:
http://febook.hzfe.org/awesome-interview/
相關問題
-
Babel 是什麼
-
Babel 有什麼用
-
壓縮代碼如何實現
回答關鍵點
JS 編譯器
AST
插件系統
Babel 是 JavaScript 編譯器:他能讓開發者在開發過程中,直接使用各類方言(如 TS、Flow、JSX)或新的語法特性,而不需要考慮運行環境,因爲 Babel 可以做到按需轉換爲低版本支持的代碼;Babel 內部原理是將 JS 代碼轉換爲 AST,對 AST 應用各種插件進行處理,最終輸出編譯後的 JS 代碼。
知識點深入
1. AST 抽象語法樹
簡單定義:以樹的形式來表現編程語言的語法結構。
利用在線 playground 調試,可以對 AST 有個直觀感受:生成的樹有多個節點,節點有不同的類型,不同類型節點有不同的屬性。
const custom = "HZFE";
AST 是源代碼的高效表示,能便捷的表示大多數編程語言的結構。適用於做代碼分析或轉換等需求。之所以用樹來進行分析或轉換,是因爲樹能使得程序中的每一節點恰好被訪問一次(前序或後續遍歷)。
常見使用場景:代碼壓縮混淆功能可以藉助 AST 來實現:分析 AST,基於各種規則進行優化(如 IF 語句優化;移除不可訪問代碼;移除 debugger 等),從而生成更小的 AST 樹,最終輸出精簡的代碼結果。
2. Babel 編譯流程
三大步驟
-
**解析階段:**Babel 默認使用 @babel/parser 將代碼轉換爲 AST。解析一般分爲兩個階段:詞法分析和語法分析。
**詞法分析:**對輸入的字符序列做標記化 (tokenization) 操作。
**語法分析:**處理標記與標記之間的關係,最終形成一顆完整的 AST 結構。
-
**轉換階段:**Babel 使用 @babel/traverse 提供的方法對 AST 進行深度優先遍歷,調用插件對關注節點的處理函數,按需對 AST 節點進行增刪改操作。
-
**生成階段:**Babel 默認使用 @babel/generator 將上一階段處理後的 AST 轉換爲代碼字符串。
3. Babel 插件系統
Babel 的核心模塊 @babel/core,@babel/parser,@babel/traverse 和 @babel/generator 提供了完整的編譯流程。而具體的轉換邏輯需要插件來完成。
在使用 Babel 時,我們可通過配置文件指定 plugin 和 preset。而 preset 可以是 plugin 和 preset 以及其他配置的集合。Babel 會遞歸讀取 preset,最終獲取一個大的 plugins 數組,用於後續使用。
常見 presets
-
@babel/preset-env
-
@babel/preset-typescript
-
@babel/preset-react
-
@babel/preset-flow
最常見的 @babel/preset-env 預設,包含了一組最新瀏覽器已支持的 ES 語法特性,並且可以通過配置目標運行環境範圍,自動按需引入插件。
編寫 Babel 插件
Babel 插件的寫法是藉助訪問者模式(Visitor Pattern)對關注的節點定義處理函數。參考一個簡單 Babel 插件例子:
module.exports = function () {
return {
pre() {},
// 在 visitor 下掛載各種感興趣的節點類型的監聽方法
visitor: {
/**
* 對 Identify 類型的節點進行處理
* @param {NodePath} path
*/
Identifier(path) {
path.node.name = path.node.name.toUpperCase();
},
},
post() {},
};
};
使用該 Babel 插件的效果如下:
// input
// index.js
function hzfe() {}
// .babelrc
{
"plugins": ["babel-plugin-yourpluginname"]
}
// output function HZFE() {}
#### **深入 Babel 轉換階段**
在轉換階段,Babel 的相關方法會獲得一個插件數組變量,用於後續的操作。插件結構可參考以下接口。
interface Plugin { key: string | undefined | null; post: Function | void; pre: Function | void; visitor: Object; parserOverride: Function | void; generatorOverride: Function | void; // ... }
轉換階段,Babel 會按以下順序執行。詳細邏輯可查看源碼:
* 執行所有插件的 pre 方法。
* 按需執行 visitor 中的方法。
* 執行所有插件的 post 方法。
一般來說,寫 Babel 插件主要使用到的是 visitor 對象,這個 visitor 對象中會書寫對於關注的 AST 節點的處理邏輯。而上面執行順序中的第二步所指的 visitor 對象,是整合自各插件的 visitor,最終形成一個大的 visitor 對象,大致的數據結構可參考以下接口:
// 書寫插件時的 visitor 結構 interface VisitorInPlugin { [ASTNodeTypeName: string]: | Function | { enter?: Function; exit?: Function; }; }
// babel 最終整合的 visitor 結構 interface VisitorInTransform { [ASTNodeTypeName: string]: { // 不同插件對相同節點的處理會合併爲數組 enter?: Function[]; exit?: Function[]; }; }
在對 AST 進行深度優先遍歷的過程中,會創建 TraversalContext 對象來把控對 NodePath 節點的訪問,訪問時調用對節點所定義的處理方法,從而實現按需執行 visitor 中的方法。詳細實現請看 babel-traverse 中的源碼。
**參考資料**
--------
1. AST:https://en.wikipedia.org/wiki/Abstract_syntax_tree
2. Babel-handbook:https://github.com/jamiebuilds/babel-handbook
3. estree:https://github.com/estree/estree
4. 訪問者模式:https://en.wikipedia.org/wiki/Visitor_pattern
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Tlqqjh7m7cp3H3F2QSTB5w