Esbuild 爲何那麼快

全文 2500 字,閱讀耗時約 20 分鐘,歡迎點贊關注轉發。

Esbuild 是什麼

Esbuild 是一個很是新的模塊打包工具,它提供了與 Webpack、Rollup、Parcel 等工具類似的資源打包能力,卻有着高的離譜的性能優點:javascript

從上到下,耗時逐步上升達到數百倍的差別,這個巨大的性能優點使得 Esbuild 在一衆基於 Node 的構建工具中迅速躥紅,特別是 Vite 2.0 宣佈使用 Esbuild 預構建依賴後,前端社區關於它的討論熱度逐漸上升。css

那麼問題來了,這是怎麼作到的?我翻閱了不少資料後,總結了一些關鍵因素:前端

下面展開細講。vue

爲何快

語言優點

大多數前端打包工具都是基於 JavaScript 實現的,而 Esbuild 則選擇使用 Go 語言編寫,兩種語言各自有其擅長的場景,可是在資源打包這種 CPU 密集場景下,Go 更具性能優點,差距有多大呢?好比計算 50 次斐波那契數列,JS 版本:java

function fibonacci(num) {
    if (num < 2) {
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2)
}

(() => {
    let cursor = 0;
    while (cursor < 50) {
        fibonacci(cursor++)
    }
})()

Go 版本:webpack

package main

func fibonacci(num int) int{
    if num<2{
        return 1
    }
    
    return fibonacci(num-1) + fibonacci(num-2)
}

func main(){
    for i := 0; i<50; i++{
        fibonacci(i)
    }
}

JavaScript 版本執行耗時大約爲 332.58s,Go 版本執行耗時大約爲 147.08s,二者相差約 1.25 倍,這個簡單實驗並不能精肯定量兩種語言的性能差異,但感官上仍是能明顯感知 Go 語言在 CPU 密集場景下會有更好的性能表現。git

歸根到底,雖然現代 JS 引擎與10年前相比有巨大的提高,但 JavaScript 本質上依然是一門解釋型語言,JavaScript 程序每次執行都須要先由解釋器一邊將源碼翻譯成機器語言,一邊調度執行;而 Go 是一種編譯型語言,在編譯階段就已經將源碼轉譯爲機器碼,啓動時只須要直接執行這些機器碼便可。也就意味着,Go 語言編寫的程序比 JavaScript 少了一個動態解釋的過程。github

這種語言層面的差別在打包場景下特別突出,說的誇張一點,JavaScript 運行時還在解釋代碼的時候,Esbuild 已經在解析用戶代碼;JavaScript 運行時解釋完代碼剛準備啓動的時候,Esbuild 可能已經打包完畢,退出進程了!web

因此在編譯運行層面,Go 前置了源碼編譯過程,相對 JavaScript 邊解釋邊運行的方式有更高的執行性能。算法

多線程優點

Go 天生具備多線程運行能力,而 JavaScript 本質上是一門單線程語言,直到引入 WebWorker 規範以後纔有可能在瀏覽器、Node 中實現多線程操做。

我曾經研讀過 Rollup、Webpack 的代碼,就我熟知的範圍內二者均未使用 WebWorker 提供的多線程能力。反觀 Esbuild,它最核心的賣點就是性能,它的實現算法通過很是精心的設計,儘量飽和地使用各個 CPU 核,特別是打包過程的解析、代碼生成階段已經實現徹底並行處理。

除了 CPU 指令運行層面的並行外,Go 語言多個線程之間還能共享相同的內存空間,而 JavaScript 的每一個線程都有本身獨有的內存堆。這意味着 Go 中多個處理單元,例如解析資源 A 的線程,能夠直接讀取資源 B 線程的運行結果,而在 JavaScript 中相同的操做須要調用通信接口 woker.postMessage 在線程間複製數據。

因此在運行時層面,Go 擁有自然的多線程能力,更高效的內存使用率,也就意味着更高的運行性能。

節制

對,沒錯,節制!

Esbuild 並非另外一個 Webpack,它僅僅提供了構建一個現代 Web 應用所需的最小功能集合,將來也不會大規模加入咱們業已熟悉的各種構建特性。最新版本 Esbuild 的主要功能特性有:

  • 支持 js、ts、jsx、css、json、文本、圖片等資源
  • 增量更新
  • Sourcemap
  • 開發服務器支持
  • 代碼壓縮
  • Code split
  • Tree shaking
  • 插件支持

能夠看到,這份列表中支持的資源類型、工程化特性很是少,甚至並不足以支撐一個大型項目的開發需求。在這以外,官網明確聲明將來沒有計劃支持以下特性:

  • Elm, Svelte, Vue, Angular 等代碼文件格式
  • Ts 類型檢查
  • AST 相關操做 API
  • Hot Module Replace
  • Module Federation

並且,Esbuild 所設計的插件系統也無心覆蓋以上這些場景,這就意味着第三方開發者沒法經過插件這種無侵入的方式實現上述功能,emmm,能夠預見將來可能會出現不少魔改版本。

Esbuild 只解決一部分問題,因此它的架構複雜度相對較小,相對地編碼複雜度也會小不少,相對於 Webpack、Rollup 等大一統的工具,也天然更容易把性能作到極致。節制的功能設計還能帶來另一個好處:徹底爲性能定製的各類附加工具。

定製

回顧一下,在 Webpack、Rollup 這類工具中,咱們不得不使用不少額外的第三方插件來解決各類工程需求,好比:

  • 使用 babel 實現 ES 版本轉譯
  • 使用 eslint 實現代碼檢查
  • 使用 TSC 實現 ts 代碼轉譯與代碼檢查
  • 使用 less、stylus、sass 等 css 預處理工具

咱們已經徹底習慣了這種方式,甚至以爲事情就應該是這樣的,大多數人可能根本沒有意識到事情能夠有另外一種解決方案。Esbuild 起了個頭,選擇徹底!徹底重寫整套編譯流程所須要用到的全部工具!這意味着它須要重寫 js、ts、jsx、json 等資源文件的加載、解析、連接、代碼生成邏輯。

開發成本很高,並且可能被動陷入封閉的風險,但收益也是巨大的,它能夠一路貫徹原則,以性能爲最高優先級定製編譯的各個階段,好比說:

  • 重寫 ts 轉譯工具,徹底拋棄 ts 類型檢查,只作代碼轉換
  • 大多數打包工具把詞法分析、語法分析、符號聲明等步驟拆解爲多個高內聚低耦合的處理單元,各個模塊職責分明,可讀性、可維護性較高。而 Esbuild 則堅持性能第一原則,不惜採用反直覺的設計模式,將多個處理算法混合在一塊兒下降編譯過程數據流轉所帶來的性能損耗
  • 一致的數據結構,以及衍生出的高效緩存策略,下一節細講

這種深度定製一方面下降了設計成本,可以保持編譯鏈條的架構一致性;一方面可以貫徹性能第一的原則,確保每一個環節以及環節之間交互性能的最優。雖然伴隨着功能、可讀性、可維護性層面的的犧牲,但在編譯性能方面幾乎作到了極致。

結構一致性

上一節咱們講到 Esbuild 選擇重寫包括 js、ts、jsx、css 等語言在內的轉譯工具,因此它更能保證源代碼在編譯步驟之間的結構一致性,好比在 Webpack 中使用 babel-loader 處理 JavaScript 代碼時,可能須要通過屢次數據轉換:

  • Webpack 讀入源碼,此時爲字符串形式
  • Babel 解析源碼,轉換爲 AST 形式
  • Babel 將源碼 AST 轉換爲低版本 AST
  • Babel 將低版本 AST generate 爲低版本源碼,字符串形式
  • Webpack 解析低版本源碼
  • Webpack 將多個模塊打包成最終產物

源碼須要經歷 string => AST => AST => string => AST => string ,在字符串與 AST 之間反覆橫跳。

而 Esbuild 重寫大多數轉譯工具以後,可以在多個編譯階段共用類似的 AST 結構,儘量減小字符串到 AST 的結構轉換,提高內存使用效率。

總結

單純從編譯性能的維度看,Esbuild 確實完勝世面上全部打包框架,差距甚至能在百倍之大:

耗時 性能差別 速度 產物大小
esbuild 0.11s 1x 1198.5 kloc/s 0.97mb
esbuild (1 thread) 0.40s 4x 329.6 kloc/s 0.97mb
webpack 4 19.14s 174x 6.9 kloc/s 1.26mb
parcel 1 22.41s 204x 5.9 kloc/s 1.56mb
webpack 5 25.61s 233x 5.1 kloc/s 1.26mb
parcel 2 31.39s 285x 4.2 kloc/s 0.97mb

但這是有代價的,刨除語言層面的自然優點外,在功能層面它直接放棄對 less、stylus、sass、vue、angular 等資源的支持,放棄 MF、HMR、TS 類型檢查等功能,正如做者所說:

This will involve saying "no" to requests for adding major features to esbuild itself. I don't think esbuild should become an all-in-one solution for all frontend needs\!

在我看來,Esbuild 當下與將來都不能替代 Webpack,它不適合直接用於生產環境,而更適合做爲一種偏底層的模塊打包工具,須要在它的基礎上二次封裝,擴展出一套既兼顧性能又有完備工程化能力的工具鏈,例如 Snowpack, Vite, SvelteKit, Remix Run 等。

總的來講,Esbuild 提供了一種新的設計思路,值得學習瞭解,但對大多數業務場景還不適合直接投入生產使用。

相關文章
相關標籤/搜索