博客 / 詳情

返回

Babel 的原理

完整高頻題庫倉庫地址:https://github.com/hzfe/aweso...

完整高頻題庫閲讀地址:https://febook.hzfe.org/

相關問題

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

三大步驟

圖片

  1. 解析階段:Babel 默認使用 @babel/parser 將代碼轉換為 AST。解析一般分為兩個階段:詞法分析和語法分析。
  • 詞法分析:對輸入的字符序列做標記化(tokenization)操作。
  • 語法分析:處理標記與標記之間的關係,最終形成一顆完整的 AST 結構。
  1. 轉換階段:Babel 使用 @babel/traverse 提供的方法對 AST 進行深度優先遍歷,調用插件對關注節點的處理函數,按需對 AST 節點進行增刪改操作。
  2. 生成階段: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 會按以下順序執行。詳細邏輯可查看源碼:

  1. 執行所有插件的 pre 方法。
  2. 按需執行 visitor 中的方法。
  3. 執行所有插件的 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
  2. Babel-handbook
  3. estree
  4. 訪問者模式
user avatar laughingzhu 頭像 tigerandflower 頭像 yaofly 頭像 flymon 頭像 buxia97 頭像 ailim 頭像 tingzhong666 頭像 pugongyingxiangyanghua 頭像 201926 頭像 b_a_r_a_n 頭像 light_5cfbb652e97ce 頭像 nihaojob 頭像
41 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.