Babel 的原理

作者:HZFEStudio

來源:SegmentFault 思否社區

完整高頻題庫倉庫地址:

https://github.com/hzfe/awesome-interview

完整高頻題庫閱讀地址:

http://febook.hzfe.org/awesome-interview/

相關問題

回答關鍵點

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 編譯流程

三大步驟

  1. **解析階段:**Babel 默認使用 @babel/parser 將代碼轉換爲 AST。解析一般分爲兩個階段:詞法分析和語法分析。

    **詞法分析:**對輸入的字符序列做標記化 (tokenization) 操作。

    **語法分析:**處理標記與標記之間的關係,最終形成一顆完整的 AST 結構。

  2. **轉換階段:**Babel 使用 @babel/traverse 提供的方法對 AST 進行深度優先遍歷,調用插件對關注節點的處理函數,按需對 AST 節點進行增刪改操作。

  3. **生成階段:**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 預設,包含了一組最新瀏覽器已支持的 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