常見問題

這是關於 esbuild 的常見問題彙整。您也可以在 GitHub 問題追蹤器 上提問。

為什麼 esbuild 如此快速?

原因有幾個

這些因素每一個都只會帶來一些加速,但加起來它們可以讓打包器比現今其他常用的打包器快上好幾個數量級。

基準測試詳細資訊

以下是每個基準測試的詳細資訊

JavaScript 基準測試
esbuild
0.39 秒
parcel 2
14.91 秒
rollup 4 + terser
34.10 秒
webpack 5
41.21 秒
0 秒
10 秒
20 秒
30 秒
40 秒

此基準測試透過將 three.js 函式庫複製 10 次,並從頭開始建立一個單一程式包,且不使用任何快取,來近似一個大型 JavaScript 程式碼庫。可以在 esbuild repo 中使用 make bench-three 來執行基準測試。

打包器 時間 相對減速 絕對速度 輸出大小
esbuild 0.39 秒 1 倍 1403.7 kloc/s 5.80mb
parcel 2 14.91 秒 38 倍 36.7 kloc/s 5.78mb
rollup 4 + terser 34.10 秒 87 倍 16.1 kloc/s 5.82mb
webpack 5 41.21 秒 106 倍 13.3 kloc/s 5.84mb

每個報告的時間都是三趟執行中最好的。我使用 --bundle --minify --sourcemap 來執行 esbuild。我使用 @rollup/plugin-terser 外掛程式,因為 Rollup 本身不支援壓縮。Webpack 5 使用 --mode=production --devtool=sourcemap。Parcel 2 使用預設選項。絕對速度是根據總行數計算,包括註解和空白行,目前為 547,441。這些測試是在配備 16gb RAM 的 6 核 2019 MacBook Pro 上執行,並已停用 macOS Spotlight

TypeScript 基準測試
esbuild
0.10 秒
parcel 2
6.91 秒
webpack 5
16.69 秒
0 秒
5 秒
10 秒
15 秒

此基準測試使用舊的 Rome 程式碼庫(在他們重新用 Rust 編寫之前)來近似一個大型 TypeScript 程式碼庫。所有程式碼都必須合併成一個單一的壓縮程式包,並附有原始碼對應表,而且產生的程式包必須能正常運作。可以在 esbuild repo 中使用 make bench-rome 來執行基準測試。

打包器 時間 相對減速 絕對速度 輸出大小
esbuild 0.10 秒 1 倍 1318.4 kloc/s 0.97mb
parcel 2 6.91ѕ 69 倍 16.1 kloc/s 0.96mb
webpack 5 16.69ѕ 167 倍 8.3 kloc/s 1.27mb

每個報告的時間都是三趟執行中最好的。我使用 --bundle --minify --sourcemap --platform=node 來執行 esbuild。Webpack 5 使用 ts-loader,並設定 transpileOnly: true--mode=production --devtool=sourcemap。Parcel 2 在 package.json 中使用 "engines": "node"。絕對速度是根據總行數計算,包括註解和空白行,目前為 131,836。這些測試是在配備 16gb RAM 的 6 核 2019 MacBook Pro 上執行,並已停用 macOS Spotlight

結果不包含 Rollup,因為我無法讓它運作,原因與 TypeScript 編譯有關。我嘗試了 @rollup/plugin-typescript,但無法停用類型檢查,我也嘗試了 @rollup/plugin-sucrase,但沒有辦法提供 tsconfig.json 檔案(這是正確路徑解析所需要的)。

即將推出的路線圖

這些功能已經在進行中,並且是首要優先事項

這些是潛在的未來功能,但可能不會發生,或可能在更有限的範圍內發生

在那之後,我會認為 esbuild 相對完整。我計畫讓 esbuild 達到一個大致穩定的狀態,然後停止累積更多功能。這將涉及對在 esbuild 本身中加入主要功能的要求說「不」。我不認為 esbuild 應該成為所有前端需求的一體化解決方案。特別是,我想避免「webpack 設定」模型的痛苦和問題,其中底層工具太過靈活,而可用性受到影響。

例如,我計畫在 esbuild 的核心本身加入這些功能

我希望我加入 esbuild 的可擴充性點(外掛程式API)將使 esbuild 可用於包含在更自訂的建置工作流程中,但我並非預期或打算讓這些可擴充性點涵蓋所有使用案例。如果你有非常自訂的需求,你應該使用其他工具。我也希望 esbuild 激勵其他建置工具透過大規模修改其實作來大幅提升效能,讓所有人都能受益,而不僅僅是使用 esbuild 的人。

我計畫在 esbuild 達到穩定後,繼續維護 esbuild 現有範圍內的所有內容。這表示實作對新發布的 JavaScript 和 TypeScript 語法功能的支援,例如。

生產就緒

這個專案尚未達到 1.0.0 版本,仍處於積極開發中。話雖如此,它已經遠遠超過 alpha 階段,而且相當穩定。我認為它是一個後期 beta 版。對一些早期採用者來說,這表示它已經足夠好,可以用於實際事物。一些其他人認為這表示 esbuild 尚未準備好。本節不會試圖說服你採取任何一方的立場。它只是試圖提供足夠的資訊,讓你能夠自行決定是否要使用 esbuild 作為你的套件管理工具。

一些數據點

防毒軟體

由於 esbuild 是以原生程式碼撰寫,因此防毒軟體有時會錯誤地將其標記為病毒。這並不表示 esbuild 是病毒。我不會發布惡意程式碼,而且我非常重視供應鏈安全性。

esbuild 的程式碼幾乎都是第一方的,只有一個 相依性 是來自 Google 的 Go 套件補充集。我的開發工作是在與我用來發布建置的機器不同的機器上進行的。我已執行其他工作以確保 esbuild 發布的建置完全可重製,且在每次發布後,發布的建置都會 自動與 在不相關環境中本地建置的建置進行比較,以確保它們在位元上相同(即 Go 編譯器本身未遭到破壞)。您也可以自己從原始碼建置 esbuild,並將您的建置成品與發布的成品進行比較,以獨立驗證這一點。

使用防毒軟體處理誤判是無可避免的現實。如果您的防毒軟體不讓您使用 esbuild,以下是可能的解決方法

過時的 Go 版本

如果您使用自動相依性漏洞掃描器,您可能會收到報告,指出 esbuild 使用的 Go 編譯器版本和/或 golang.org/x/sys(esbuild 唯一的相依性)的版本已過時。這些報告是良性的,應予以忽略。

這是因為 esbuild 的程式碼故意設計為可與 Go 1.13 相容。後來的 Go 版本已不再支援某些較舊的平台,而我希望 esbuild 能夠在這些平台上執行(例如較舊版本的 macOS)。雖然 esbuild 發布的二進位檔是用更新版本的 Go 編譯器編譯的(因此無法在較舊版本的 macOS 上執行),但您目前仍可以使用 Go 1.13 為自己編譯最新版本的 esbuild,並在較舊版本的 macOS 上使用它,因為 esbuild 的程式碼仍然可以與 Go 1.13 及其後續版本相容。

人們和/或自動化工具有時會看到 go.mod 中的 go 1.13 行,並抱怨 esbuild 發布的二進位檔是用 Go 1.13 建置的,而 Go 1.13 是 Go 的一個非常舊的版本。然而,事實並非如此。go.mod 中的那一行只指定最低編譯器版本。它與 esbuild 發布的二進位檔所建置的 Go 版本無關,後者是更新版本的 Go。 請閱讀文件。

人們有時也希望 esbuild 更新 golang.org/x/sys 相依性,因為 esbuild 使用的版本存在已知的漏洞(特別是關於 Faccessat 函數的 GO-2022-0493)。阻止 esbuild 更新到更新版本的 golang.org/x/sys 相依性的問題是,較新的版本已開始使用 unsafe.Slice 函數,該函數最早於 Go 1.17 中引入(因此無法編譯到較舊版本的 Go 中)。然而,這個漏洞報告無關緊要,因為 a) esbuild 根本不會呼叫該函數,而且 b) esbuild 是建置工具,不是沙箱,而 esbuild 的檔案系統存取並非安全性敏感的。

我不會放棄與較舊平台的相容性,並阻止某些人使用 esbuild,只為了解決無關緊要的漏洞報告。請忽略任何關於上述問題的報告。

縮小的換行符

人們有時會驚訝於 esbuild 的縮小程式通常會將 JavaScript 字串中的字元跳脫序列 \n 變更為範本字串中的換行字元。但這是故意的。這不是 esbuild 的錯誤。縮小程式的任務是產生與輸入等效的輸出,且輸出盡可能精簡。字元跳脫序列 \n 長度為兩個位元組,而換行字元長度為一個位元組。

例如,這個程式碼長度為 21 個位元組

var text="a\nb\nc\n";

而這個程式碼長度為 18 個位元組

var text=`a
b
c
`;

因此,第二個程式碼已完全縮小,而第一個程式碼則沒有。縮小程式碼並不表示將所有程式碼放在一行上。相反地,縮小程式碼表示產生等效的程式碼,且使用最少的位元組。在 JavaScript 中,未標記的範本字串等同於字串字串,因此 esbuild 在這裡執行的動作是正確的。

避免名稱衝突

在瀏覽器中執行 esbuild 輸出時,進入點模組中的頂層變數不應出現在全域範圍。如果發生這種情況,表示您未遵循 esbuild 關於輸出格式的文件,並錯誤地使用 esbuild。這不是 esbuild 的錯誤。

特別是,在瀏覽器中執行 esbuild 輸出時,您必須執行下列其中一項動作

  1. --format=iife 搭配 <script src="...">

    如果您在全域範圍執行程式碼,則應使用 --format=iife。這會讓 esbuild 的輸出包裝您的程式碼,以便在巢狀範圍中宣告頂層變數。

  2. --format=esm 搭配 <script src="..." type="module">

    如果您使用 --format=esm,則必須以模組執行您的程式碼。這會導致瀏覽器封裝您的程式碼,以便在巢狀範圍中宣告頂層變數。

--format=esm<script src="..."> 搭配使用會以微妙且令人困惑的方式中斷您的程式碼(省略 type="module" 表示所有頂層變數最終都會出現在全域範圍中,然後會與其他 JavaScript 檔案中具有相同名稱的頂層變數發生衝突)。

頂層 var

人們有時會驚訝於 esbuild 有時會將頂層 letconstclass 宣告改寫為 var 宣告。這是出於以下幾個原因

請注意,esbuild 沒有保留頂層 TDZ 的副作用,因為模組可能需要延遲初始化(如上所述),這表示要將宣告與初始化分開。頂層符號的 TDZ 檢查理論上仍然可以透過產生額外的程式碼來支援,該程式碼會在每次使用頂層符號之前進行檢查,如果尚未初始化,則會擲回例外(有效地手動實作真實 JavaScript VM 會執行的動作)。然而,這對於程式碼大小和執行時間來說似乎都是過度的負擔,而且似乎不是生產導向套件應該做的事情。