API
API 可以使用三種語言中的其中一種存取:命令列、JavaScript 和 Go。這三種語言的概念和參數在很大程度上是相同的,因此它們將在此處一起呈現,而不是為每種語言提供單獨的文件。您可以使用每個程式碼範例右上角的 CLI
、JS
和 Go
標籤在語言之間切換。每種語言的一些具體資訊
CLI:如果您使用命令列 API,了解這些標記有下列三種形式可能會有所幫助:
--foo
、--foo=bar
或--foo:bar
。--foo
形式用於啟用布林標記,例如--minify
,--foo=bar
形式用於具有單一值且僅指定一次的標記,例如--platform=
,而--foo:bar
形式用於具有多個值且可以重新指定多次的標記,例如--external:
。JavaScript:如果您使用 JavaScript,請務必查看以下 JavaScript 特定詳情 和 瀏覽器 區段。您還可能會發現 esbuild 的 TypeScript 類型定義 作為參考很有用。
Go:如果您使用 Go,您可能會發現自動產生的 esbuild Go 文件作為參考很有用。針對兩個公開的 Go 套件都有單獨的文件:
pkg/api
和pkg/cli
。
#概觀
兩個最常用的 esbuild API 是 build 和 transform。每個 API 都在下面以高階方式描述,接著是每個個別 API 選項的文件。
#建置
這是 esbuild 的主要介面。您通常會傳遞一個或多個 進入點 檔案進行處理以及各種選項,然後 esbuild 會將結果寫回檔案系統。以下是一個啟用 套件 與 輸出目錄 的簡單範例
esbuild app.ts --bundle --outdir=dist
import * as esbuild from 'esbuild'
let result = await esbuild.build({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'dist',
})
console.log(result)
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "dist",
})
if len(result.Errors) != 0 {
os.Exit(1)
}
}
進階使用 build API 包含設定一個長期執行的建置內容。此內容在 JS 和 Go 中是一個明確的物件,但在 CLI 中是隱含的。使用特定內容所執行的所有建置會共用相同的建置選項,後續的建置會以遞增方式執行(亦即,它們會重複使用先前建置的一些工作以提升效能)。這對於開發很有用,因為 esbuild 可以在你工作時在背景中為你重新建置你的應用程式。
有三個不同的遞增建置 API
- 監控模式會指示 esbuild 監控檔案系統,並在你編輯和儲存可能會使建置失效的檔案時自動為你重新建置。以下是一個範例
esbuild app.ts --bundle --outdir=dist --watch [watch] build finished, watching for changes...
let ctx = await esbuild.context({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'dist',
})
await ctx.watch()
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "dist",
})
err2 := ctx.Watch(api.WatchOptions{})
- 提供服務模式會啟動一個本機開發伺服器,提供最新建置的結果。接收到的要求會自動啟動新的建置,因此當你在瀏覽器中重新載入頁面時,你的網路應用程式會始終是最新的。以下是一個範例
esbuild app.ts --bundle --outdir=dist --serve > Local: http://127.0.0.1:8000/ > Network: http://192.168.0.1:8000/ 127.0.0.1:61302 - "GET /" 200 [1ms]
let ctx = await esbuild.context({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'dist',
})
let { host, port } = await ctx.serve()
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "dist",
})
server, err2 := ctx.Serve(api.ServeOptions{})
- 重新建置模式讓你手動呼叫建置。這在將 esbuild 與其他工具整合時很有用(例如,使用自訂檔案監控器或開發伺服器,而不是 esbuild 內建的)。以下是一個範例
# The CLI does not have an API for "rebuild"
let ctx = await esbuild.context({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'dist',
})
for (let i = 0; i < 5; i++) {
let result = await ctx.rebuild()
}
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "dist",
})
for i := 0; i < 5; i++ {
result := ctx.Rebuild()
}
這三個遞增建置 API 可以結合使用。若要啟用 即時重新載入(在你編輯和儲存檔案時自動重新載入頁面),你需要在同一個內容上同時啟用 監控 和 提供服務。
當你完成使用內容物件時,你可以呼叫內容上的 dispose()
來等待現有的建置完成,停止監控和/或提供服務模式,並釋放資源。
建置和內容 API 都採用下列選項
#轉換
這是 build 的一個受限特殊案例,它會轉換一個表示隔離環境中記憶體檔案的程式碼字串,該環境與任何其他檔案完全斷開連接。常見用途包括縮小程式碼和將 TypeScript 轉換為 JavaScript。以下是一個範例
echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'
let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
loader: 'ts',
})
console.log(result)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
ts := "let x: number = 1"
result := api.Transform(ts, api.TransformOptions{
Loader: api.LoaderTS,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
在某些使用案例中,將字串而非檔案作為輸入更符合人體工學。檔案系統隔離有某些優點(例如在瀏覽器中運作,不受鄰近的 package.json
檔案影響)和某些缺點(例如無法與 bundling 或 plugins 搭配使用)。如果你的使用案例不符合轉換 API,則你應該改用更通用的 build API。
轉換 API 會採用下列選項
#JS 特定的詳細資料
esbuild 的 JS API 有非同步和同步兩種形式。非同步 API 是建議使用的,因為它可以在所有環境中運作,而且速度更快、功能更強大。同步 API 只能在 node 中運作,而且只能執行某些操作,但有時在某些特定於 node 的情況下是必要的。詳細說明如下
#非同步 API
非同步 API 呼叫會使用承諾傳回其結果。請注意,由於使用了 import
和頂層 await
關鍵字,你可能必須在 node 中使用 .mjs
檔案副檔名
import * as esbuild from 'esbuild'
let result1 = await esbuild.transform(code, options)
let result2 = await esbuild.build(options)
優點
- 你可以將 plugins 與非同步 API 搭配使用
- 目前的執行緒不會被封鎖,因此你可以同時執行其他工作
- 你可以同時執行許多非同步 esbuild API 呼叫,然後將這些呼叫分散到所有可用的 CPU 上,以獲得最佳效能
缺點
- 使用承諾可能會導致程式碼更混亂,特別是在 CommonJS 中,其中不提供 頂層 await
- 在必須同步的情況下無法運作,例如在
require
內部.extensions
#同步 API
同步 API 呼叫會內嵌傳回其結果
let esbuild = require('esbuild')
let result1 = esbuild.transformSync(code, options)
let result2 = esbuild.buildSync(options)
優點
- 避免使用承諾可能會導致程式碼更簡潔,特別是在不提供 頂層 await 時
- 在必須同步的情況下運作,例如在
require
內部.extensions
缺點
- 你無法將 plugins 與同步 API 搭配使用,因為 plugins 是非同步的
- 它會封鎖目前的執行緒,因此你無法同時執行其他工作
- 使用同步 API 會阻止 esbuild 並行執行 esbuild API 呼叫
#在瀏覽器中
esbuild API 也可以在 Web Worker 中使用 WebAssembly 在瀏覽器中執行。要利用這項優勢,您需要安裝 esbuild-wasm
套件,而不是 esbuild
套件
npm install esbuild-wasm
瀏覽器的 API 與節點的 API 類似,但您需要先呼叫 initialize()
,並且需要傳遞 WebAssembly 二進位檔的 URL。API 的同步版本也不可用。假設您正在使用打包器,它看起來會像這樣
import * as esbuild from 'esbuild-wasm'
await esbuild.initialize({
wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
})
let result1 = await esbuild.transform(code, options)
let result2 = esbuild.build(options)
如果您已經從工作人員執行此程式碼,並且不希望 initialize
建立另一個工作人員,您可以傳遞 worker:
給它。然後它將在呼叫 initialize
的執行緒中建立一個 WebAssembly 模組。
您也可以在 HTML 檔案中使用 <script>
標籤將 esbuild 的 API 作為腳本標籤,而不需要使用打包器,方法是載入 lib/browser.min.js
檔案。在這種情況下,API 會建立一個名為 esbuild
的全域變數,其中包含 API 物件
<script src="./node_modules/esbuild-wasm/lib/browser.min.js"></script>
<script>
esbuild.initialize({
wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
}).then(() => {
...
})
</script>
如果您想將此 API 與 ECMAScript 模組一起使用,您應該匯入 esm/browser.min.js
檔案
<script type="module">
import * as esbuild from './node_modules/esbuild-wasm/esm/browser.min.js'
await esbuild.initialize({
wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
})
...
</script>
#一般選項
#套件
支援:建立
套件檔案表示將任何匯入的相依項內嵌到檔案本身中。這個程序是遞迴的,因此相依項的相依項(依此類推)也會被內嵌。預設情況下,esbuild 不會 套件輸入檔案。必須像這樣明確啟用套件
esbuild in.js --bundle
import * as esbuild from 'esbuild'
console.log(await esbuild.build({
entryPoints: ['in.js'],
bundle: true,
outfile: 'out.js',
}))
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"in.js"},
Bundle: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
請參閱 入門指南,以取得使用實際程式碼套件的範例。
請注意,套件與檔案串接不同。在啟用套件的情況下傳遞多個輸入檔案給 esbuild,將會建立多個獨立的套件,而不是將輸入檔案串接在一起。要使用 esbuild 將一組檔案串接在一起,請將它們全部匯入單一進入點檔案,並使用 esbuild 套件該檔案。
#不可分析的匯入
匯入路徑目前僅在它們是字串文字或 glob 模式 時才會被套件。其他形式的匯入路徑不會被套件,而是會原樣保留在產生的輸出中。這是因為套件是編譯時期的作業,而 esbuild 不支援所有形式的執行時期路徑解析。以下是幾個範例
// Analyzable imports (will be bundled by esbuild)
import 'pkg';
import('pkg');
require('pkg');
import(`./locale-${foo}.json`);
require(`./locale-${foo}.json`);
// Non-analyzable imports (will not be bundled by esbuild)
import(`pkg/${foo}`);
require(`pkg/${foo}`);
['pkg'].map(require);
處理無法分析的匯入方式是將包含此問題程式碼的套件標記為 外部,使其不會包含在套件中。然後,您需要確保在執行階段,您的套件化程式碼可以使用外部套件的副本。
有些套件化器,例如 Webpack,會嘗試支援所有形式的執行階段路徑解析,方法是將所有可能存取的檔案包含在套件中,然後在執行階段模擬檔案系統。然而,執行階段檔案系統模擬不在範圍內,也不會在 esbuild 中實作。如果您真的需要套件化執行此操作的程式碼,您可能需要使用另一個套件化器,而不是 esbuild。
#Glob 型式匯入
現在可以在某些有限的情況下套件化在執行階段評估的匯入路徑。匯入路徑表達式必須是字串串接的形式,且必須以 ./
或 ../
開頭。字串串接鏈中的每個非字串表達式都會在 Glob 模式中變成萬用字元。一些範例
// These two forms are equivalent
const json1 = require('./data/' + kind + '.json')
const json2 = require(`./data/${kind}.json`)
當您執行此操作時,esbuild 會搜尋檔案系統中所有符合模式的檔案,並將所有檔案包含在套件中,以及一個將符合的匯入路徑對應到套件化模組的對應。匯入表達式將會替換為對應的查詢。如果匯入路徑不存在於對應中,則會在執行階段擲回錯誤。產生的程式碼看起來會像這樣(省略不重要的部分以簡潔)
// data/bar.json
var require_bar = ...;
// data/foo.json
var require_foo = ...;
// require("./data/**/*.json") in example.js
var globRequire_data_json = __glob({
"./data/bar.json": () => require_bar(),
"./data/foo.json": () => require_foo()
});
// example.js
var json1 = globRequire_data_json("./data/" + kind + ".json");
var json2 = globRequire_data_json(`./data/${kind}.json`);
此功能適用於 require(...)
和 import(...)
,因為它們都可以接受執行階段表達式。它不適用於 import
和 export
陳述式,因為它們無法接受執行階段表達式。如果您要防止 esbuild 嘗試套件化這些匯入,您應該將字串串接表達式移到 require(...)
或 import(...)
之外。例如
// This will be bundled
const json1 = require('./data/' + kind + '.json')
// This will not be bundled
const path = './data/' + kind + '.json'
const json2 = require(path)
請注意,使用此功能表示 esbuild 可能會執行大量檔案系統 I/O,以尋找所有可能符合模式的檔案。這是設計使然,而不是錯誤。如果這是個問題,有兩種方法可以減少 esbuild 執行的檔案系統 I/O 數量
最簡單的方法是將所有想要匯入的檔案,放入給定執行時間匯入表達式的子目錄中,然後將子目錄包含在模式中。由於 esbuild 在模式比對期間不會考慮
..
路徑元素,因此這會限制 esbuild 在該子目錄中進行搜尋。另一種方法是完全阻止 esbuild 搜尋任何子目錄。esbuild 使用的模式比對演算法僅允許萬用字元比對包含
/
路徑分隔符號的內容,前提是該萬用字元在模式中其前面有一個/
。因此,例如'./data/' +
會將x + '.json' x
與任何子目錄中的任何內容比對,而'./data-' +
則只會將x + '.json' x
與頂層目錄中的任何內容比對(但不會與任何子目錄中的內容比對)。
#取消
支援:建立
如果您使用 重建 手動呼叫增量建置,您可能想要使用這個取消 API 來提早結束目前的建置,以便您可以開始新的建置。您可以這樣做
# The CLI does not have an API for "cancel"
import * as esbuild from 'esbuild'
import process from 'node:process'
let ctx = await esbuild.context({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'www',
logLevel: 'info',
})
// Whenever we get some data over stdin
process.stdin.on('data', async () => {
try {
// Cancel the already-running build
await ctx.cancel()
// Then start a new build
console.log('build:', await ctx.rebuild())
} catch (err) {
console.error(err)
}
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "www",
LogLevel: api.LogLevelInfo,
})
if err != nil {
os.Exit(1)
}
// Whenever we get some data over stdin
buf := make([]byte, 100)
for {
if n, err := os.Stdin.Read(buf); err != nil || n == 0 {
break
}
go func() {
// Cancel the already-running build
ctx.Cancel()
// Then start a new build
result := ctx.Rebuild()
fmt.Fprintf(os.Stderr, "build: %v\n", result)
}()
}
}
請務必等到取消操作完成後再開始新的建置(也就是在使用 JavaScript 時,await
傳回的承諾),否則下一個 重建 會提供給您剛剛取消但尚未結束的建置。請注意,無論建置是否已取消,外掛 on-end 回呼 仍會執行。
#即時重新載入
支援:建立
即時重新載入是一種開發方法,您可以在程式碼編輯器中同時開啟並查看瀏覽器。當您編輯並儲存原始碼時,瀏覽器會自動重新載入,而重新載入的應用程式版本會包含您的變更。這表示您可以更快地進行反覆運算,因為您不必在每次變更後手動切換到瀏覽器、重新載入,然後再切換回程式碼編輯器。例如,在變更 CSS 時,這非常有幫助。
esbuild 沒有直接提供即時重新載入的 API。相反地,您可以透過結合 監控模式(在您編輯並儲存檔案時自動開始建置)和 提供模式(提供最新的建置,但會封鎖直到完成)以及一小段您只在開發期間新增到應用程式的用戶端 JavaScript 程式碼,來建構即時重新載入。
esbuild app.ts --bundle --outdir=www --watch --servedir=www
import * as esbuild from 'esbuild'
let ctx = await esbuild.context({
entryPoints: ['app.ts'],
bundle: true,
outdir: 'www',
})
await ctx.watch()
let { host, port } = await ctx.serve({
servedir: 'www',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Outdir: "www",
})
if err != nil {
os.Exit(1)
}
err2 := ctx.Watch(api.WatchOptions{})
if err2 != nil {
os.Exit(1)
}
result, err3 := ctx.Serve(api.ServeOptions{
Servedir: "www",
})
if err3 != nil {
os.Exit(1)
}
}
第二步是在您的 JavaScript 中新增一些訂閱 /esbuild
伺服器傳送事件 來源的程式碼。當您收到 change
事件時,您可以重新載入頁面以取得最新版本的應用程式。您可以在一行程式碼中執行此操作
new EventSource('/esbuild').addEventListener('change', () => location.reload())
這樣就完成了!如果您在瀏覽器中載入應用程式,現在頁面應該會在您編輯並儲存檔案時自動重新載入(假設沒有建置錯誤)。
這只應在開發期間包含,不應包含在生產中。在生產中移除此程式碼的一種方法是使用 if 陳述式(例如 if (!window.IS_PRODUCTION)
)來保護它,然後使用 定義 在生產中將 window.IS_PRODUCTION
設定為 true
。
#即時重新載入注意事項
像這樣實作即時重新載入有一些已知的注意事項
這些事件僅在 esbuild 的輸出變更時觸發。當與正在監控的建置無關的檔案變更時,它們不會觸發。如果你的 HTML 檔案參照了 esbuild 不認識的其他檔案,且這些檔案有變更,你可以手動重新載入頁面,或實作你自己的即時重新載入基礎架構,而不是使用 esbuild 的內建行為。
EventSource
API 應該會自動為你重新連線。然而,Firefox 中有一個錯誤,如果伺服器暫時無法存取,就會中斷此功能。解決方法是使用任何其他瀏覽器,如果發生這種情況,手動重新載入頁面,或撰寫更複雜的程式碼,在連線錯誤時手動關閉並重新建立EventSource
物件。瀏覽器供應商已決定不實作沒有 TLS 的 HTTP/2。這表示在使用
http://
協定時,每個/esbuild
事件來源會佔用你寶貴的 6 個同時每個網域 HTTP/1.1 連線之一。因此,如果你開啟超過六個使用此即時重新載入技術的 HTTP 標籤,你將無法在其中一些標籤中使用即時重新載入(其他功能也可能會中斷)。解決方法是啟用https://
協定。
#CSS 的熱重新載入
change
事件還包含其他資訊,以啟用更進階的使用案例。它目前包含 added
、removed
和 updated
陣列,其中包含自上次建置以來已變更檔案的路徑,可以用以下 TypeScript 介面來描述
interface ChangeEvent {
added: string[]
removed: string[]
updated: string[]
}
以下程式碼範例啟用了 CSS 的「熱重新載入」,這表示 CSS 會自動在原處更新,而不會重新載入頁面。如果出現的事件與 CSS 無關,則整個頁面將會重新載入作為備用方案
new EventSource('/esbuild').addEventListener('change', e => {
const { added, removed, updated } = JSON.parse(e.data)
if (!added.length && !removed.length && updated.length === 1) {
for (const link of document.getElementsByTagName("link")) {
const url = new URL(link.href)
if (url.host === location.host && url.pathname === updated[0]) {
const next = link.cloneNode()
next.href = updated[0] + '?' + Math.random().toString(36).slice(2)
next.onload = () => link.remove()
link.parentNode.insertBefore(next, link.nextSibling)
return
}
}
}
location.reload()
})
#JavaScript 的熱重新載入
esbuild 目前尚未實作 JavaScript 的熱重新載入。因為 CSS 是無狀態的,所以可以透明地實作 CSS 的熱重新載入,但 JavaScript 是有狀態的,因此你無法像對待 CSS 那樣透明地實作 JavaScript 的熱重新載入。
一些其他開發伺服器還是實作了 JavaScript 的熱重新載入,但它需要額外的 API,有時需要特定於架構的破解,有時會在編輯階段引入暫態狀態相關錯誤。這超出了 esbuild 的範圍。如果你需要 JavaScript 的熱重新載入,歡迎使用其他工具,而不是 esbuild。
不過,透過 esbuild 的即時重新載入,你可以將應用程式的目前 JavaScript 狀態保留在 sessionStorage
中,以便在重新載入頁面後更輕鬆地還原應用程式的 JavaScript 狀態。如果你的應用程式載入得很快(為了使用者的利益,它本來就應該很快),使用 JavaScript 即時重新載入幾乎和使用 JavaScript 熱重新載入一樣快。
#平台
預設情況下,esbuild 的捆綁器設定為產生供瀏覽器使用的程式碼。如果你的捆綁程式碼打算在節點中執行,你應該將平台設定為 node
esbuild app.js --bundle --platform=node
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
platform: 'node',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Platform: api.PlatformNode,
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
當平台設定為 browser
(預設值)時
當 捆綁 已啟用時,預設輸出 格式 設定為
iife
,它會將產生的 JavaScript 程式碼包裝在立即呼叫函式表示式中,以防止變數外洩到全域範圍。如果套件在
package.json
檔案中為browser
欄位指定一個對應,esbuild 會使用該對應以特定檔案或模組取代它們對瀏覽器友善的版本。例如,套件可能會包含path
的替代品path-browserify
。主欄位 設定設定為
browser,
,但有一些額外的特殊行為:如果套件提供module, main module
和main
進入點,但沒有提供browser
進入點,則如果使用require()
匯入該套件,將使用main
代替module
。此行為改善了與 CommonJS 模組的相容性,這些模組透過將函式指定給module.exports
來匯出函式。如果你想要停用此額外的特殊行為,你可以明確將 主欄位 設定設定為browser,
。module, main 條件 設定會自動包含
browser
條件。這會改變package.json
檔案中exports
欄位的解譯方式,以偏好瀏覽器特定的程式碼。如果沒有設定自訂 條件,也會包含 Webpack 特定的
module
條件。套件作者使用module
條件來提供 CommonJS 檔案可樹狀搖晃的 ESM 替代方案,而不會產生 雙重套件危害。你可以透過明確設定一些自訂條件(即使是空清單)來防止包含module
條件。使用 build API 時,如果所有 縮小 選項都已啟用,則所有
process.
表達式會自動 定義 為env. NODE_ENV "production"
,否則為"development"
。這僅在process
、process.env
和process.env.NODE_ENV
尚未定義時才會發生。此替換對於避免基於 React 的程式碼立即崩潰是必要的(因為process
是節點 API,而不是網路 API)。字元序列
</script>
會在 JavaScript 程式碼中跳脫,而字元序列</style>
會在 CSS 程式碼中跳脫。這是為了避免將 esbuild 的輸出直接內嵌到 HTML 檔案中。這可以使用 esbuild 的 支援 功能停用,方法是將inline-script
(適用於 JavaScript)和/或inline-style
(適用於 CSS)設定為false
。
當平台設定為 node
當 綑綁 已啟用時,預設輸出 格式 會設定為
cjs
,代表 CommonJS(節點使用的模組格式)。使用export
陳述式的 ES6 風格匯出將會轉換為 CommonJSexports
物件上的 getter。主要欄位 設定會設定為
main,
。這表示對於同時提供module module
和main
的套件,不太可能發生樹狀搖晃,因為樹狀搖晃適用於 ECMAScript 模組,但不適用於 CommonJS 模組。不幸的是,有些套件錯誤地將
module
視為「瀏覽器程式碼」,而不是「ECMAScript 模組程式碼」,因此需要此預設行為以確保相容性。如果您想啟用樹狀搖晃,並且知道這樣做是安全的,您可以手動將 主要欄位 設定設定為module,
。main 條件 設定會自動包含
node
條件。這會改變package.json
檔案中exports
欄位的解譯方式,以優先使用特定於節點的程式碼。如果沒有設定自訂 條件,也會包含 Webpack 特定的
module
條件。套件作者使用module
條件來提供 CommonJS 檔案可樹狀搖晃的 ESM 替代方案,而不會產生 雙重套件危害。你可以透過明確設定一些自訂條件(即使是空清單)來防止包含module
條件。當 格式 設定為
cjs
,但進入點是 ESM 時,esbuild 會為任何命名匯出新增特殊註解,以使用 ESM 語法從產生的 CommonJS 檔案匯入這些命名匯出。節點文件包含有關 節點偵測 CommonJS 命名匯出 的更多資訊。binary
載入器會使用節點內建的Buffer.from
API 將綑綁中嵌入的 base64 資料解碼為Uint8Array
。這比 esbuild 否則能執行的速度更快,因為它是由節點以原生程式碼實作的。
當平台設定為 neutral
當 綑綁 已啟用時,預設輸出 格式 會設定為
esm
,它使用 ECMAScript 2015(即 ES6)引入的export
語法。如果您認為此預設不適當,您可以變更輸出格式。主要欄位 設定預設為空。如果您想使用 npm 風格的套件,您可能必須將其設定為其他內容,例如節點使用的標準主要欄位的
main
。條件 設定不會自動包含任何特定於平台的值。
#重建
支援:建立
如果你的使用案例涉及重複使用相同的選項呼叫 esbuild 的 build API,你可能需要使用此 API。例如,如果你正在實作自己的檔案監控服務,這會很有用。重建比重新建置更有效率,因為前次建置的一些資料會快取,如果自上次建置以來原始檔案沒有變更,就可以重複使用。重建 API 目前使用兩種快取形式
檔案儲存在記憶體中,如果自上次建置以來檔案的元資料沒有變更,就不會從檔案系統重新讀取。此最佳化只適用於檔案系統路徑。它不適用於 外掛程式 建立的虛擬模組。
已剖析的 AST 儲存在記憶體中,如果自上次建置以來檔案內容沒有變更,就不會重新剖析 AST。此最佳化適用於外掛程式建立的虛擬模組以及檔案系統模組,只要虛擬模組路徑保持不變。
以下是執行重建的方法
# The CLI does not have an API for "rebuild"
import * as esbuild from 'esbuild'
let ctx = await esbuild.context({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
})
// Call "rebuild" as many times as you want
for (let i = 0; i < 5; i++) {
let result = await ctx.rebuild()
}
// Call "dispose" when you're done to free up resources
ctx.dispose()
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Outfile: "out.js",
})
if err != nil {
os.Exit(1)
}
// Call "Rebuild" as many times as you want
for i := 0; i < 5; i++ {
result := ctx.Rebuild()
if len(result.Errors) > 0 {
os.Exit(1)
}
}
// Call "Dispose" when you're done to free up resources
ctx.Dispose()
}
#提供服務
支援:建立
提供服務模式會啟動一個網路伺服器,將你的程式碼提供給裝置上的瀏覽器。以下是一個範例,它將 src/app.ts
套件成 www/js/app.js
,然後透過 http://localhost:8000/
提供 www
目錄。
esbuild src/app.ts --outdir=www/js --bundle --servedir=www
import * as esbuild from 'esbuild'
let ctx = await esbuild.context({
entryPoints: ['src/app.ts'],
outdir: 'www/js',
bundle: true,
})
let { host, port } = await ctx.serve({
servedir: 'www',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"src/app.ts"},
Outdir: "www/js",
Bundle: true,
})
if err != nil {
os.Exit(1)
}
server, err2 := ctx.Serve(api.ServeOptions{
Servedir: "www",
})
if err2 != nil {
os.Exit(1)
}
// Returning from main() exits immediately in Go.
// Block forever so we keep serving and don't exit.
<-make(chan struct{})
}
如果你使用以下內容建立 www/index.html
檔案,當你導覽至 http://localhost:8000/
時,儲存在 src/app.ts
中的程式碼就會載入。
<script src="js/app.js"></script>
使用 esbuild 內建的網路伺服器而不是其他網路伺服器的一個好處是,每當你重新載入時,esbuild 提供的檔案總是最新。其他開發設定不一定會這樣。一個常見的設定是執行一個本機檔案監控器,在輸入檔案變更時重建輸出檔案,然後再單獨執行一個本機檔案伺服器來提供這些輸出檔案。但是,這表示在編輯後重新載入可能會重新載入舊的輸出檔案,如果重建尚未完成。使用 esbuild 的網路伺服器,每個傳入的請求都會在尚未進行中時啟動重建,然後等待目前的重建完成後再提供檔案。這表示 esbuild 永遠不會提供過期的建置結果。
請注意,此網路伺服器僅供開發使用。請勿在生產環境中使用。
#引數
提供服務 API 的引數如下
# Enable serve mode
--serve
# Set the port
--serve=9000
# Set the host and port (IPv4)
--serve=127.0.0.1:9000
# Set the host and port (IPv6)
--serve=[::1]:9000
# Set the directory to serve
--servedir=www
# Enable HTTPS
--keyfile=your.key --certfile=your.cert
# Specify a fallback HTML file
--serve-fallback=some-file.html
interface ServeOptions {
port?: number
host?: string
servedir?: string
keyfile?: string
certfile?: string
fallback?: string
onRequest?: (args: ServeOnRequestArgs) => void
}
interface ServeOnRequestArgs {
remoteAddress: string
method: string
path: string
status: number
timeInMS: number
}
type ServeOptions struct {
Port uint16
Host string
Servedir string
Keyfile string
Certfile string
Fallback string
OnRequest func(ServeOnRequestArgs)
}
type ServeOnRequestArgs struct {
RemoteAddress string
Method string
Path string
Status int
TimeInMS int
}
主機
預設情況下,esbuild 會在所有 IPv4 網路介面上提供網路伺服器。這對應於主機地址
0.0.0.0
。如果你想設定不同的主機(例如,只在127.0.0.1
回授介面上提供服務,而不對網路公開任何內容),你可以使用此引數指定主機。如果您需要使用 IPv6 而不是 IPv4,您只需要指定一個 IPv6 主機地址。IPv6 中等效於
127.0.0.1
回送介面的地址為::1
,而等效於 IPv6 中0.0.0.0
通用介面的地址為::
。port
HTTP 埠可選擇在此設定。如果省略,它將預設為開放埠,優先使用 8000 到 8009 範圍內的埠。
servedir
這是一個額外內容目錄,供 esbuild 的 HTTP 伺服器使用,當收到的要求與任何產生的輸出檔案路徑不符時,會提供此目錄內容,而不是 404 錯誤。這讓您可以將 esbuild 用作一般用途的本機網路伺服器。
例如,您可能想要建立一個
index.html
檔案,然後將servedir
設定為"."
,以提供目前目錄(包含index.html
檔案)。如果您沒有設定servedir
,esbuild 將只會提供建置結果,而不會提供任何其他檔案。keyfile
和certfile
如果您使用
keyfile
和certfile
將私密金鑰和憑證傳遞給 esbuild,esbuild 的網路伺服器將使用https://
協定,而不是http://
協定。有關更多資訊,請參閱 啟用 HTTPS。fallback
這是一個 HTML 檔案,供 esbuild 的 HTTP 伺服器使用,當收到的要求與任何產生的輸出檔案路徑不符時,會提供此檔案,而不是 404 錯誤。您可以將其用於自訂的「找不到」頁面。您也可以將其用作 單頁式應用程式 的進入點,該應用程式會變更目前的 URL,因此需要同時從許多不同的 URL 提供。
onRequest
這會針對每個收到的要求呼叫一次,並提供一些關於該要求的資訊。CLI 使用此回呼函數為每個要求列印出一個記錄訊息。時間欄位是產生要求資料的時間,但不包含將要求串流傳輸到用戶端的時間。
請注意,這是要求完成後才呼叫的。無法使用此回呼函數以任何方式修改要求。如果您想要執行此操作,您應該 在 esbuild 之前放置一個代理伺服器。
#傳回值
# The CLI will print the host and port like this:
> Local: http://127.0.0.1:8000/
interface ServeResult {
host: string
port: number
}
type ServeResult struct {
Host string
Port uint16
}
主機
這是最後由網路伺服器使用的主機。除非自訂主機已設定,否則將會是
0.0.0.0
(亦即在所有可用的網路介面中提供服務)。如果您使用 CLI 且主機為0.0.0.0
,所有可用的網路介面將會列印為主機。port
這是最後由網路伺服器使用的埠。如果您未指定埠,您會想要使用此埠,因為 esbuild 最後會挑選一個隨意的開放埠,而您需要知道它挑選哪個埠才能連線到它。
#啟用 HTTPS
預設情況下,esbuild 的網路伺服器使用 http://
協定。然而,某些現代網路功能無法在 HTTP 網站使用。如果您想要使用這些功能,您需要告訴 esbuild 改用 https://
協定。
使用 esbuild 啟用 HTTPS
產生自簽憑證。有許多方法可以做到這一點。以下提供一種方法,假設您已安裝
openssl
指令openssl req -x509 -newkey rsa:4096 -keyout your.key -out your.cert -days 9999 -nodes -subj /CN=127.0.0.1
使用
keyfile
和certfile
提供參數將your.key
和your.cert
傳遞給 esbuild。當您載入您的網頁時,按掉瀏覽器中令人害怕的警告(自簽憑證不安全,但這沒關係,因為我們只是在進行本機開發)。
如果您有比這更複雜的需求,您仍然可以 在 esbuild 之前放置一個代理程式,並改用它來使用 HTTPS。請注意,如果您在載入網頁時看到訊息 Client
,表示您使用的是不正確的協定。在瀏覽器的網址列中將 http://
替換為 https://
。
請記住,esbuild 的 HTTPS 支援與安全性無關。在 esbuild 中啟用 HTTPS 的唯一原因是瀏覽器讓在不使用這些額外步驟的情況下使用某些現代網路功能進行本機開發變得不可能。請勿將 esbuild 的開發伺服器用於任何需要安全性的用途。它僅用於本機開發,而且完全沒有考慮到生產環境。
#自訂伺服器行為
無法連接到 esbuild 的本機伺服器以自訂伺服器本身的行為。相反地,應該在 esbuild 之前放置一個代理程式來自訂行為。
以下是使用節點內建的 http
模組建立代理伺服器的簡單範例。它會新增自訂的 404 頁面,取代 esbuild 的預設 404 頁面
import * as esbuild from 'esbuild'
import http from 'node:http'
// Start esbuild's server on a random local port
let ctx = await esbuild.context({
// ... your build options go here ...
})
// The return value tells us where esbuild's local server is
let { host, port } = await ctx.serve({ servedir: '.' })
// Then start a proxy server on port 3000
http.createServer((req, res) => {
const options = {
hostname: host,
port: port,
path: req.url,
method: req.method,
headers: req.headers,
}
// Forward each incoming request to esbuild
const proxyReq = http.request(options, proxyRes => {
// If esbuild returns "not found", send a custom 404 page
if (proxyRes.statusCode === 404) {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end('<h1>A custom 404 page</h1>')
return
}
// Otherwise, forward the response from esbuild to the client
res.writeHead(proxyRes.statusCode, proxyRes.headers)
proxyRes.pipe(res, { end: true })
})
// Forward the body of the request to esbuild
req.pipe(proxyReq, { end: true })
}).listen(3000)
這段程式碼會在隨機的本機埠上啟動 esbuild 的伺服器,然後在埠 3000 上啟動代理伺服器。在開發期間,你會在瀏覽器中載入 http://localhost:3000,它會與代理伺服器通訊。此範例示範如何在 esbuild 處理請求後修改回應,但你也可以在 esbuild 處理請求之前修改或取代請求。
你可以使用像這樣的代理伺服器做很多事情,包括
- 插入你自己的 404 頁面(如上述範例)
- 自訂檔案系統中路由到檔案的對應
- 將某些路由重新導向到 API 伺服器,而不是 esbuild
如果你有更進階的需求,也可以使用像 nginx 這樣的真實代理伺服器。
#Tsconfig
支援:建立
通常,build API 會自動偵測 tsconfig.json
檔案,並在建置期間讀取其內容。但是,你也可以設定自訂的 tsconfig.json
檔案來使用。如果你需要使用不同的設定對同一份程式碼進行多次建置,這會很有用
esbuild app.ts --bundle --tsconfig=custom-tsconfig.json
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
bundle: true,
tsconfig: 'custom-tsconfig.json',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Bundle: true,
Tsconfig: "custom-tsconfig.json",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#Tsconfig raw
此選項可用於將你的 tsconfig.json
檔案傳遞給 transform API,它不會存取檔案系統。它也可以用於將你的 tsconfig.json
檔案的內容內嵌傳遞給 build API,而不用將它寫入檔案。使用它的方式如下
echo 'class Foo { foo }' | esbuild --loader=ts --tsconfig-raw='{"compilerOptions":{"useDefineForClassFields":false}}'
import * as esbuild from 'esbuild'
let ts = 'class Foo { foo }'
let result = await esbuild.transform(ts, {
loader: 'ts',
tsconfigRaw: `{
"compilerOptions": {
"useDefineForClassFields": false,
},
}`,
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
ts := "class Foo { foo }"
result := api.Transform(ts, api.TransformOptions{
Loader: api.LoaderTS,
TsconfigRaw: `{
"compilerOptions": {
"useDefineForClassFields": false,
},
}`,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#監控
支援:建立
啟用監控模式會指示 esbuild 監聽檔案系統的變更,並在可能使建置失效的檔案變更時自動重新建置。使用它的方式如下
esbuild app.js --outfile=out.js --bundle --watch [watch] build finished, watching for changes...
import * as esbuild from 'esbuild'
let ctx = await esbuild.context({
entryPoints: ['app.js'],
outfile: 'out.js',
bundle: true,
})
await ctx.watch()
console.log('watching...')
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.js"},
Outfile: "out.js",
Bundle: true,
Write: true,
})
if err != nil {
os.Exit(1)
}
err2 := ctx.Watch(api.WatchOptions{})
if err2 != nil {
os.Exit(1)
}
fmt.Printf("watching...\n")
// Returning from main() exits immediately in Go.
// Block forever so we keep watching and don't exit.
<-make(chan struct{})
}
如果你想要在未來某個時間點停止監控模式,你可以呼叫 context 物件上的 dispose
來終止檔案監控器
# Use Ctrl+C to stop the CLI in watch mode
import * as esbuild from 'esbuild'
let ctx = await esbuild.context({
entryPoints: ['app.js'],
outfile: 'out.js',
bundle: true,
})
await ctx.watch()
console.log('watching...')
await new Promise(r => setTimeout(r, 10 * 1000))
await ctx.dispose()
console.log('stopped watching')
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
import "time"
func main() {
ctx, err := api.Context(api.BuildOptions{
EntryPoints: []string{"app.js"},
Outfile: "out.js",
Bundle: true,
Write: true,
})
if err != nil {
os.Exit(1)
}
err2 := ctx.Watch(api.WatchOptions{})
if err2 != nil {
os.Exit(1)
}
fmt.Printf("watching...\n")
time.Sleep(10 * time.Second)
ctx.Dispose()
fmt.Printf("stopped watching\n")
}
esbuild 中的監控模式使用輪詢,而非作業系統特定的檔案系統 API,以實現可攜性。輪詢系統的設計是使用相對較少的 CPU,相較於一次掃描整個目錄樹的傳統輪詢系統。檔案系統仍會定期掃描,但每次掃描只會檢查檔案的隨機子集,這表示檔案的變更會在變更後不久被偵測到,但並不一定會立即偵測到。
根據目前的啟發法,大型專案應每 2 秒左右完全掃描一次,因此在最糟的情況下,可能需要長達 2 秒才能偵測到變更。但是,在偵測到變更後,變更的路徑會被加入最近變更路徑的簡短清單中,並在每次掃描時檢查,因此最近變更檔案的後續變更應會幾乎立即被偵測到。
請注意,如果你不想使用基於輪詢的方法,仍然可以使用 esbuild 的 重建 API 和你選擇的檔案監控程式庫,自行實作監控模式。
如果你使用 CLI,請記住當 esbuild 的標準輸入關閉時,監控模式將會終止。這可防止 esbuild 意外地比父程序存活更久,並意外地繼續消耗系統資源。如果你有需要 esbuild 在父程序結束後仍持續監控的用例,你可以使用 --watch=
,而非 --watch
。
#輸入
#進入點
支援:建立
這是一個檔案陣列,每個檔案都作為綑綁演算法的輸入。它們被稱為「進入點」,因為每個進入點都應為要評估的初始腳本,然後載入它所代表的程式碼的所有其他面向。你無需使用 <script>
標籤在你的網頁中載入許多程式庫,而是可以使用 import
陳述式將它們匯入你的進入點(或匯入到然後匯入你的進入點的另一個檔案中)。
簡單的應用程式只需要一個進入點,但如果有多個邏輯上獨立的程式碼群組,例如主執行緒和工作執行緒,或具有獨立且關聯性較低的區域(例如登入頁、編輯器頁和設定頁)的應用程式,則其他進入點會很有用。分開的進入點有助於引入關注點分離,並有助於減少瀏覽器需要下載的不必要程式碼量。如果適用,啟用 程式碼分割 可以進一步減少下載大小,當瀏覽進入點與已瀏覽的第一個頁面共用一些已下載程式碼的第二個頁面時。
指定進入點的簡單方法是傳遞檔案路徑陣列
esbuild home.ts settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['home.ts', 'settings.ts'],
bundle: true,
write: true,
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"home.ts", "settings.ts"},
Bundle: true,
Write: true,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
這將產生兩個輸出檔案,out/home.js
和 out/settings.js
,分別對應於兩個進入點 home.ts
和 settings.ts
。
若要進一步控制輸出檔案的路徑如何從對應的輸入進入點衍生,你應該查看這些選項
此外,您也可以使用替代的進入點語法,為每個個別進入點指定完全自訂的輸出路徑
esbuild out1=home.ts out2=settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: [
{ out: 'out1', in: 'home.ts'},
{ out: 'out2', in: 'settings.ts'},
],
bundle: true,
write: true,
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPointsAdvanced: []api.EntryPoint{{
OutputPath: "out1",
InputPath: "home.ts",
}, {
OutputPath: "out2",
InputPath: "settings.ts",
}},
Bundle: true,
Write: true,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
這會產生兩個輸出檔案,out/out1.js
和 out/out2.js
,分別對應於兩個進入點 home.ts
和 settings.ts
。
#載入器
此選項會變更給定的輸入檔案的解譯方式。例如,js
載入器會將檔案解譯為 JavaScript,而 css
載入器會將檔案解譯為 CSS。請參閱 內容類型 頁面,以取得所有內建載入器的完整清單。
為給定的檔案類型設定載入器,讓您可以使用 import
陳述式或 require
呼叫載入該檔案類型。例如,將 .png
檔案副檔名設定為使用 資料 URL 載入器,表示匯入 .png
檔案會提供給您包含該影像內容的資料 URL
import url from './example.png'
let image = new Image
image.src = url
document.body.appendChild(image)
import svg from './example.svg'
let doc = new DOMParser().parseFromString(svg, 'application/xml')
let node = document.importNode(doc.documentElement, true)
document.body.appendChild(node)
可以使用 build API 呼叫,以類似下列方式組合上述程式碼
esbuild app.js --bundle --loader:.png=dataurl --loader:.svg=text
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
loader: {
'.png': 'dataurl',
'.svg': 'text',
},
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Loader: map[string]api.Loader{
".png": api.LoaderDataURL,
".svg": api.LoaderText,
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
如果您使用 stdin 中的輸入來使用 build API,則此選項的指定方式會有所不同,因為 stdin 沒有檔案副檔名。使用 build API 為 stdin 設定載入器如下所示
echo 'import pkg = require("./pkg")' | esbuild --loader=ts --bundle
import * as esbuild from 'esbuild'
await esbuild.build({
stdin: {
contents: 'import pkg = require("./pkg")',
loader: 'ts',
resolveDir: '.',
},
bundle: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
Stdin: &api.StdinOptions{
Contents: "import pkg = require('./pkg')",
Loader: api.LoaderTS,
ResolveDir: ".",
},
Bundle: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
transform API 呼叫只會使用單一載入器,因為它不涉及與檔案系統的互動,因此不會處理檔案副檔名。為 transform API 設定載入器(在本例中為 ts
載入器)如下所示
echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'
let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
loader: 'ts',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
ts := "let x: number = 1"
result := api.Transform(ts, api.TransformOptions{
Loader: api.LoaderTS,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#Stdin
支援:建立
通常 build API 呼叫會將一個或多個檔案名稱作為輸入。不過,此選項可用於在檔案系統中完全沒有模組的情況下執行組合。它稱為「stdin」,因為它對應於在命令列上將檔案導向 stdin。
除了指定 stdin 檔案的內容外,您還可以選擇指定解析目錄(用於確定相對匯入的位置)、sourcefile(錯誤訊息和來源對應中要使用的檔案名稱)以及 loader(用於確定如何詮釋檔案內容)。CLI 沒有辦法指定解析目錄。相反地,它會自動設定為目前的作業目錄。
以下是使用此功能的方法
echo 'export * from "./another-file"' | esbuild --bundle --sourcefile=imaginary-file.js --loader=ts --format=cjs
import * as esbuild from 'esbuild'
let result = await esbuild.build({
stdin: {
contents: `export * from "./another-file"`,
// These are all optional:
resolveDir: './src',
sourcefile: 'imaginary-file.js',
loader: 'ts',
},
format: 'cjs',
write: false,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
Stdin: &api.StdinOptions{
Contents: "export * from './another-file'",
// These are all optional:
ResolveDir: "./src",
Sourcefile: "imaginary-file.js",
Loader: api.LoaderTS,
},
Format: api.FormatCommonJS,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#輸出內容
#標語
使用此功能在產生的 JavaScript 和 CSS 檔案開頭插入任意字串。這通常用於插入註解
esbuild app.js --banner:js=//comment --banner:css=/*comment*/
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
banner: {
js: '//comment',
css: '/*comment*/',
},
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Banner: map[string]string{
"js": "//comment",
"css": "/*comment*/",
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
這類似於 頁尾,但會插入在結尾而不是開頭。
請注意,如果您要將非註解程式碼插入 CSS 檔案,請注意 CSS 會忽略所有出現在非 @import
規則(@charset
規則除外)之後的 @import
規則,因此使用標語來注入 CSS 規則可能會意外停用外部樣式表的匯入。
#字元集
預設情況下,esbuild 的輸出僅限於 ASCII。任何非 ASCII 字元都會使用反斜線跳脫序列進行跳脫。原因之一是因為非 ASCII 字元預設會被瀏覽器誤解,這會造成混淆。您必須明確將 <meta
新增到您的 HTML 或使用正確的 Content-
標頭提供服務,以避免瀏覽器破壞您的程式碼。另一個原因是非 ASCII 字元可能會大幅 降低瀏覽器的剖析器速度。但是,使用跳脫序列會讓產生的輸出稍微變大,也更難閱讀。
如果您希望 esbuild 在不使用跳脫序列的情況下列印原始字元,並且您已確保瀏覽器會將您的程式碼詮釋為 UTF-8,您可以透過設定字元集來停用字元跳脫
echo 'let π = Math.PI' | esbuild
let \u03C0 = Math.PI;
echo 'let π = Math.PI' | esbuild --charset=utf8
let π = Math.PI;
import * as esbuild from 'esbuild'
let js = 'let π = Math.PI'
(await esbuild.transform(js)).code
'let \\u03C0 = Math.PI;\n'
(await esbuild.transform(js, {
charset: 'utf8',
})).code
'let π = Math.PI;\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "let π = Math.PI"
result1 := api.Transform(js, api.TransformOptions{})
if len(result1.Errors) == 0 {
fmt.Printf("%s", result1.Code)
}
result2 := api.Transform(js, api.TransformOptions{
Charset: api.CharsetUTF8,
})
if len(result2.Errors) == 0 {
fmt.Printf("%s", result2.Code)
}
}
一些注意事項
這尚未跳脫嵌入在正規表示式中的非 ASCII 字元。這是因為 esbuild 目前完全不剖析正規表示式的內容。儘管有此限制,此標記仍已新增,因為它對於不包含此類案例的程式碼仍然有用。
此標記不適用於註解。我相信保留註解中的非 ASCII 資料應該是沒問題的,因為即使編碼錯誤,執行時間環境也應該完全忽略所有註解的內容。例如,V8 部落格文章 提到了避免完全解碼註解內容的最佳化。而且,除了與授權相關的註解之外,esbuild 會將所有其他註解移除。
此選項同時套用至所有輸出檔案類型(JavaScript、CSS 和 JSON)。因此,如果您將網路伺服器設定為傳送正確的
Content-
標頭,並想要使用 UTF-8 字元集,請確定您的網路伺服器已設定為將Type .js
和.css
檔案視為 UTF-8。
#頁尾
使用此選項在產生的 JavaScript 和 CSS 檔案結尾插入任意字串。這通常用於插入註解
esbuild app.js --footer:js=//comment --footer:css=/*comment*/
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
footer: {
js: '//comment',
css: '/*comment*/',
},
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Footer: map[string]string{
"js": "//comment",
"css": "/*comment*/",
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
這類似於 標題,它會插入在開頭而不是結尾。
#格式
這會設定產生 JavaScript 檔案的輸出格式。目前可以設定三個可能值:iife
、cjs
和 esm
。當未指定輸出格式時,如果已啟用 套件(如下所述),esbuild 會為您挑選輸出格式,或如果已停用 套件,則不會進行任何格式轉換。
#IIFE
iife
格式代表「立即呼叫函式表達式」,並預計在瀏覽器中執行。將您的程式碼包裝在函式表達式中可確保程式碼中的任何變數不會意外與全域範圍中的變數衝突。如果您的進入點有您想要在瀏覽器中公開為全域的匯出,您可以使用 全域名稱 設定來設定該全域的名稱。當未指定輸出格式、已啟用 套件,且 平台 設定為 browser
(這是預設值)時,iife
格式會自動啟用。指定 iife
格式如下所示
echo 'alert("test")' | esbuild --format=iife
(() => {
alert("test");
})();
import * as esbuild from 'esbuild'
let js = 'alert("test")'
let result = await esbuild.transform(js, {
format: 'iife',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "alert(\"test\")"
result := api.Transform(js, api.TransformOptions{
Format: api.FormatIIFE,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#CommonJS
cjs
格式代表「CommonJS」,並預計在 node 中執行。它假設環境包含 exports
、require
和 module
。使用 ECMAScript 模組語法的匯出進入點會轉換為模組,並在 exports
上為每個匯出名稱提供 getter。當未指定輸出格式、已啟用 套件,且 平台 設定為 node
時,cjs
格式會自動啟用。指定 cjs
格式如下所示
echo 'export default "test"' | esbuild --format=cjs
...
var stdin_exports = {};
__export(stdin_exports, {
default: () => stdin_default
});
module.exports = __toCommonJS(stdin_exports);
var stdin_default = "test";
import * as esbuild from 'esbuild'
let js = 'export default "test"'
let result = await esbuild.transform(js, {
format: 'cjs',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "export default 'test'"
result := api.Transform(js, api.TransformOptions{
Format: api.FormatCommonJS,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#ESM
esm
格式代表「ECMAScript 模組」。它假設環境支援 import
和 export
語法。以 CommonJS 模組語法匯出的進入點將轉換為 module.exports
值的單一 default
匯出。當未指定輸出格式、啟用 綑綁,且 平台 設定為 neutral
時,esm
格式將自動啟用。指定 esm
格式如下所示
echo 'module.exports = "test"' | esbuild --format=esm
...
var require_stdin = __commonJS({
"<stdin>"(exports, module) {
module.exports = "test";
}
});
export default require_stdin();
import * as esbuild from 'esbuild'
let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
format: 'esm',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "module.exports = 'test'"
result := api.Transform(js, api.TransformOptions{
Format: api.FormatESModule,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
esm
格式可以在瀏覽器或 node 中使用,但您必須明確將其載入為模組。如果您從另一個模組 import
它,則會自動執行此操作。否則
- 在瀏覽器中,您可以使用
<script
載入模組。不要忘記src=" file.js" type=" module"> </script> type="
,因為這會以微妙且令人困惑的方式中斷您的程式碼(省略module" type="
表示所有頂層變數都將出現在全域範圍中,然後會與其他 JavaScript 檔案中具有相同名稱的頂層變數發生衝突)。module"
- 在 node 中,您可以使用
node
載入模組。請注意,除非您已在file.mjs package.json
檔案中設定"type":
,否則 node 需要"module" .mjs
副檔名。您可以使用 esbuild 中的 輸出副檔名 設定自訂 esbuild 產生的檔案的輸出副檔名。您可以在 這裡進一步了解在 node 中使用 ECMAScript 模組。
#全域名稱
此選項僅在 格式 設定為 iife
(代表立即呼叫函式表達式)時才重要。它設定用於儲存進入點匯出的全域變數名稱
echo 'module.exports = "test"' | esbuild --format=iife --global-name=xyz
import * as esbuild from 'esbuild'
let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
format: 'iife',
globalName: 'xyz',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "module.exports = 'test'"
result := api.Transform(js, api.TransformOptions{
Format: api.FormatIIFE,
GlobalName: "xyz",
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
使用 iife
格式指定全域名稱將產生類似以下的程式碼
var xyz = (() => {
...
var require_stdin = __commonJS((exports, module) => {
module.exports = "test";
});
return require_stdin();
})();
全域名稱也可以是複合屬性表達式,在這種情況下,esbuild 將產生具有該屬性的全域變數。現有的全域變數衝突不會被覆寫。這可以用於實作「命名空間」,其中多個獨立腳本將其匯出新增到同一個全域物件。例如
echo 'module.exports = "test"' | esbuild --format=iife --global-name='example.versions["1.0"]'
import * as esbuild from 'esbuild'
let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
format: 'iife',
globalName: 'example.versions["1.0"]',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "module.exports = 'test'"
result := api.Transform(js, api.TransformOptions{
Format: api.FormatIIFE,
GlobalName: `example.versions["1.0"]`,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
上面使用的複合全域名稱會產生類似以下的程式碼
var example = example || {};
example.versions = example.versions || {};
example.versions["1.0"] = (() => {
...
var require_stdin = __commonJS((exports, module) => {
module.exports = "test";
});
return require_stdin();
})();
#法律評論
「法律評論」被視為 JS 中的任何陳述級評論或 CSS 中的規則級評論,其中包含 @license
或 @preserve
,或以 //!
或 /*!
開頭。預設情況下,這些評論會保留在輸出檔案中,因為這符合原始程式碼作者的意圖。但是,可以使用以下選項之一來設定此行為
none
不要保留任何法律評論。內嵌
保留所有法律評論。檔案結束
將所有法律評論移至檔案結尾。連結
將所有法律評論移至.LEGAL.txt
檔案,並透過評論連結至它們。外部
將所有法律評論移至.LEGAL.txt
檔案,但不要連結至它們。
當啟用 打包 時,預設行為為 檔案結束
,否則為 內嵌
。設定法律評論模式如下所示
esbuild app.js --legal-comments=eof
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
legalComments: 'eof',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
LegalComments: api.LegalCommentsEndOfFile,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
請注意,JS 的「陳述層級」和 CSS 的「規則層級」表示評論必須出現在允許多個陳述或規則的上下文中,例如頂層範圍或陳述或規則區塊。因此,表達式內或宣告層級的評論不被視為法律評論。
#行數限制
此設定是一種防止 esbuild 產生具有超長行的輸出檔案的方法,這有助於在執行不良的文字編輯器中編輯效能。將此設定為正整數,以指示 esbuild 在超過該位元組數後不久結束特定行。例如,這會在長行超過約 80 個字元後立即換行
esbuild app.ts --line-limit=80
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
lineLimit: 80,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
LineLimit: 80,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
行數會在超過限制後才被截斷,而不是在之前,因為在限制被超過時檢查會比預測限制即將被超過時來得簡單,而且在產生輸出檔案時避免備份和重寫會更快。因此,限制僅為近似值。
此設定適用於 JavaScript 和 CSS,即使在停用最小化時也能運作。請注意,啟用此設定會使檔案變大,因為額外的換行符號會佔用檔案中的額外空間(即使在 gzip 壓縮後)。
#分割
支援:建立
這會啟用「程式碼分割」,其有兩個目的
在多個進入點之間共用的程式碼會分割成一個獨立的共用檔案,兩個進入點都會匯入該檔案。這樣一來,如果使用者先瀏覽到一個頁面,然後再瀏覽到另一個頁面,如果共用部分已由瀏覽器下載並快取,他們就不必從頭下載第二個頁面的所有 JavaScript。
透過非同步
import()
表達式引用的程式碼將會分割成一個獨立的檔案,並且僅在評估該表達式時才載入。這允許你透過僅在啟動時下載所需的程式碼,然後在需要時延遲下載額外程式碼,來改善應用程式的初始下載時間。如果未啟用程式碼拆分,
import()
表達式會變成Promise
。這仍保留表達式的非同步語意,但表示已匯入的程式碼會包含在同一個套件中,而不是拆分到一個獨立的檔案中。.resolve() .then(() => require())
啟用程式碼拆分時,也必須使用 outdir 設定來設定輸出目錄
esbuild home.ts about.ts --bundle --splitting --outdir=out --format=esm
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['home.ts', 'about.ts'],
bundle: true,
splitting: true,
outdir: 'out',
format: 'esm',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"home.ts", "about.ts"},
Bundle: true,
Splitting: true,
Outdir: "out",
Format: api.FormatESModule,
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#輸出位置
#允許覆寫
支援:建立
啟用此設定允許輸出檔案覆寫輸入檔案。它預設未啟用,因為這樣做表示覆寫您的原始碼,如果您的程式碼未簽入,可能會導致資料遺失。但支援此功能可避免需要暫存目錄,讓某些工作流程更輕鬆。因此,當您想要故意覆寫原始碼時,可以啟用此功能
esbuild app.js --outdir=. --allow-overwrite
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
outdir: '.',
allowOverwrite: true,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Outdir: ".",
AllowOverwrite: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#資產名稱
支援:建立
當 loader 設為 file
時,此選項會控制產生額外輸出檔案的檔案名稱。它使用範本來設定輸出路徑,其中包含會在產生輸出路徑時以特定於檔案的值取代的佔位符。例如,指定資產名稱範本為 assets/
會將所有資產放入輸出目錄內的子目錄 assets
中,並在檔案名稱中包含資產的內容雜湊。這樣做看起來像這樣
esbuild app.js --asset-names=assets/[name]-[hash] --loader:.png=file --bundle --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
assetNames: 'assets/[name]-[hash]',
loader: { '.png': 'file' },
bundle: true,
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
AssetNames: "assets/[name]-[hash]",
Loader: map[string]api.Loader{
".png": api.LoaderFile,
},
Bundle: true,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
資產路徑範本中可以使用四個佔位符
[dir]
這是從包含資產檔案的目錄到 outbase 目錄的相對路徑。它的目的是幫助資產輸出路徑看起來更美觀,方法是在輸出目錄內鏡射輸入目錄結構。
[name]
這是資產的原始檔名,不含副檔名。例如,如果資產的原始名稱為
image.png
,則[name]
會在範本中以image
取代。不必使用此佔位符;它只存在於提供對使用者友善的資產名稱,以簡化除錯。[hash]
這是資產的內容雜湊,可用於避免名稱衝突。例如,您的程式碼可能會匯入
components/
和button/ icon.png components/
,在這種情況下,您需要雜湊來區分兩個都命名為select/ icon.png icon
的資產。[ext]
這是資產的副檔名(即最後一個
.
字元之後的所有內容)。它可用於將不同類型的資產放入不同的目錄。例如,--asset-names=
可能會將名為assets/ [ext]/ [name]-[hash] image.png
的資產寫出為assets/
。png/ image-CQFGD2NG.png
資產路徑範本不需要包含副檔名。資產的原始副檔名會在範本替換後自動新增到輸出路徑的結尾。
#區塊名稱
支援:建立
此選項控制在啟用區塊分割時自動產生的共用程式碼區塊的檔案名稱。它使用包含佔位符的範本設定輸出路徑,這些佔位符會在產生輸出路徑時替換為區塊的特定值。例如,指定區塊名稱範本為 chunks/
會將所有產生的區塊放入輸出目錄內的 chunks
子目錄中,並在檔案名稱中包含區塊的內容雜湊。這樣做看起來像這樣
esbuild app.js --chunk-names=chunks/[name]-[hash] --bundle --outdir=out --splitting --format=esm
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
chunkNames: 'chunks/[name]-[hash]',
bundle: true,
outdir: 'out',
splitting: true,
format: 'esm',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
ChunkNames: "chunks/[name]-[hash]",
Bundle: true,
Outdir: "out",
Splitting: true,
Format: api.FormatESModule,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
區塊路徑範本中可以使用三個佔位符
[name]
這目前永遠會是文字
chunk
,儘管此佔位符在未來的版本中可能會採用其他值。[hash]
這是區塊的內容雜湊。在產生多個共用程式碼區塊的情況下,包含此雜湊對於區分不同的區塊是必要的。
[ext]
這是區塊的檔案副檔名(即最後一個
.
字元之後的所有內容)。它可用於將不同類型的區塊放入不同的目錄中。例如,--chunk-names=
可能會將區塊寫成chunks/ [ext]/ [name]-[hash] chunks/
。css/ chunk-DEFJT7KY.css
區塊路徑範本不需要包含檔案副檔名。適當內容類型的已設定輸出副檔名會在範本替換後自動新增到輸出路徑的結尾。
請注意,此選項僅控制自動產生的共用程式碼區塊的名稱。它不控制與進入點相關的輸出檔案名稱。這些名稱目前是由原始進入點檔案相對於輸出基礎目錄的路徑決定的,而且此行為無法變更。未來會新增一個額外的 API 選項,讓您可以變更進入點輸出檔案的檔案名稱。
#條目名稱
支援:建立
此選項控制與每個輸入條目點檔案對應的輸出檔案的檔案名稱。它使用含有佔位符的範本設定輸出路徑,這些佔位符將在產生輸出路徑時以特定於檔案的值替換。例如,指定條目名稱範本為 [dir]/
會在檔案名稱中包含輸出檔案的雜湊,並將檔案放入輸出目錄中,可能在子目錄下(請參閱下方有關 [dir]
的詳細資訊)。執行此操作如下所示
esbuild src/main-app/app.js --entry-names=[dir]/[name]-[hash] --outbase=src --bundle --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['src/main-app/app.js'],
entryNames: '[dir]/[name]-[hash]',
outbase: 'src',
bundle: true,
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"src/main-app/app.js"},
EntryNames: "[dir]/[name]-[hash]",
Outbase: "src",
Bundle: true,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
條目路徑範本中可以使用四個佔位符
[dir]
這是從包含輸入條目點檔案的目錄到 outbase 目錄的相對路徑。其目的是幫助您避免不同子目錄中同名條目點之間的衝突。
例如,如果存在兩個條目點
src/
和pages/ home/ index.ts src/
,outbase 目錄為pages/ about/ index.ts src
,而條目名稱範本為[dir]/[name]
,則輸出目錄將包含pages/
和home/ index.js pages/
。如果條目名稱範本只是about/ index.js [name]
,則會導致捆綁失敗,因為輸出目錄中將會有兩個輸出路徑為index.js
的輸出檔案。[name]
這是條目點的原始檔名,不含副檔名。例如,如果輸入條目點檔案的名稱為
app.js
,則[name]
將在範本中替換為app
。[hash]
這是輸出檔案的內容雜湊,可用於最佳利用瀏覽器快取。將
[hash]
加入條目點名稱表示 esbuild 將計算一個雜湊,該雜湊與對應輸出檔案中的所有內容相關(如果 程式碼分割 處於啟用狀態,則與其匯入的任何輸出檔案相關)。雜湊的設計目的是僅在與該輸出檔案相關的任何輸入檔案變更時變更。之後,您可以讓您的網路伺服器告訴瀏覽器永遠快取這些檔案(實際上,您可以說它們會在很長一段時間後過期,例如一年後)。然後,您可以使用 metafile 中的資訊來確定哪個輸出檔案路徑對應於哪個輸入條目點,以便您知道要包含在
<script>
標籤中的路徑。[ext]
這是入口點檔案將被寫入的檔案副檔名(例如 輸出副檔名 設定,而不是原始檔案副檔名)。它可用於將不同類型的入口點放入不同的目錄。例如,
--entry-names=
可能會將entries/ [ext]/ [name] app.ts
的輸出檔案寫入entries/
。js/ app.js
入口路徑範本不需要包含檔案副檔名。適當的 輸出副檔名 會在範本替換後自動新增到輸出路徑的結尾,根據檔案類型而定。
#輸出副檔名
支援:建立
此選項可讓您自訂 esbuild 所產生檔案的檔案副檔名,改為 .js
或 .css
以外的副檔名。特別是 .mjs
和 .cjs
檔案副檔名在節點中具有特殊意義(它們分別表示 ESM 和 CommonJS 格式的檔案)。如果您使用 esbuild 來產生多個檔案,而且必須使用 輸出目錄 選項而非 輸出檔案 選項,則此選項會很有用。您可以像這樣使用它
esbuild app.js --bundle --outdir=dist --out-extension:.js=.mjs
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
outdir: 'dist',
outExtension: { '.js': '.mjs' },
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Outdir: "dist",
OutExtension: map[string]string{
".js": ".mjs",
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#輸出基礎
支援:建立
如果您的組建包含多個位於不同目錄中的入口點,則目錄結構將複製到 輸出目錄 中,相對於輸出基礎目錄。例如,如果有兩個入口點 src/
和 src/
,而輸出基礎目錄是 src
,則輸出目錄將包含 pages/
和 pages/
。以下是使用方法
esbuild src/pages/home/index.ts src/pages/about/index.ts --bundle --outdir=out --outbase=src
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: [
'src/pages/home/index.ts',
'src/pages/about/index.ts',
],
bundle: true,
outdir: 'out',
outbase: 'src',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{
"src/pages/home/index.ts",
"src/pages/about/index.ts",
},
Bundle: true,
Outdir: "out",
Outbase: "src",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
如果未指定輸出基礎目錄,則它預設為所有輸入入口點路徑中的 最低共同祖先 目錄。在上面的範例中,這是 src/
,這表示輸出目錄預設將包含 home/
和 about/
。
#輸出目錄
支援:建立
此選項設定組建作業的輸出目錄。例如,此指令將產生一個稱為 out
的目錄
esbuild app.js --bundle --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
如果輸出目錄尚未存在,則將產生該目錄,但如果其中已包含一些檔案,則不會清除該目錄。任何產生的檔案都將靜默覆寫具有相同名稱的現有檔案。如果您希望輸出目錄僅包含來自目前 esbuild 執行的檔案,則您應該在執行 esbuild 之前清除輸出目錄。
如果你的組建包含多個位於不同目錄中的進入點,目錄結構將從所有輸入進入點路徑中的 最低共祖 目錄開始複製到輸出目錄。例如,如果有兩個進入點 src/
和 src/
,輸出目錄將包含 home/
和 about/
。如果你想自訂此行為,你應該變更 outbase 目錄。
#輸出檔案
支援:建立
此選項設定組建操作的輸出檔案名稱。這僅適用於單一進入點的情況。如果有多個進入點,你必須使用 outdir 選項來指定輸出目錄。使用 outfile 的方式如下
esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Outdir: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#公開路徑
支援:建立
這與 外部檔案 載入器結合使用時很有用。預設情況下,該載入器使用 default
輸出將匯入檔案的名稱作為字串輸出。公開路徑選項讓你可以在此載入器載入的每個檔案的匯出字串前面加上一個基本路徑
esbuild app.js --bundle --loader:.png=file --public-path=https://www.example.com/v1 --outdir=out
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
loader: { '.png': 'file' },
publicPath: 'https://www.example.com/v1',
outdir: 'out',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Loader: map[string]api.Loader{
".png": api.LoaderFile,
},
Outdir: "out",
PublicPath: "https://www.example.com/v1",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#寫入
支援:建立
組建 API 呼叫可以寫入檔案系統或將會寫入的檔案作為記憶體中的緩衝區傳回。預設情況下,CLI 和 JavaScript API 會寫入檔案系統,而 Go API 則不會。若要使用記憶體中的緩衝區
import * as esbuild from 'esbuild'
let result = await esbuild.build({
entryPoints: ['app.js'],
sourcemap: 'external',
write: false,
outdir: 'out',
})
for (let out of result.outputFiles) {
console.log(out.path, out.contents, out.hash, out.text)
}
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Sourcemap: api.SourceMapExternal,
Write: false,
Outdir: "out",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
for _, out := range result.OutputFiles {
fmt.Printf("%v %v %s\n", out.Path, out.Contents, out.Hash)
}
}
hash
屬性是 contents
欄位的雜湊,已提供以供方便。雜湊演算法(目前為 XXH64)取決於實作,且可能在 esbuild 版本之間隨時變更。
#路徑解析
#別名
支援:建立
此功能讓你可以在打包時將一個套件替換為另一個套件。以下範例將套件 oldpkg
替換為套件 newpkg
esbuild app.js --bundle --alias:oldpkg=newpkg
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
write: true,
alias: {
'oldpkg': 'newpkg',
},
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Write: true,
Alias: map[string]string{
"oldpkg": "newpkg",
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
這些新的替換會在 esbuild 的所有其他路徑解析邏輯之前先發生。此功能的一個用例是在你無法控制的第三方程式碼中將僅限節點的套件替換為瀏覽器友善的套件。
請注意,當使用別名替換匯入路徑時,產生的匯入路徑會在工作目錄中解析,而不是在包含具有匯入路徑的來源檔案的目錄中解析。如有需要,可以使用 工作目錄 功能設定 esbuild 使用的工作目錄。
#條件
支援:建立
此功能控制如何詮釋 package.json
中的 exports
欄位。可以使用條件設定新增自訂條件。您可以指定任意數量的條件,而這些條件的意義完全取決於套件作者。目前 Node 只核准 development
和 production
自訂條件供推薦使用。以下是新增自訂條件 custom1
和 custom2
的範例
esbuild src/app.js --bundle --conditions=custom1,custom2
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['src/app.js'],
bundle: true,
conditions: ['custom1', 'custom2'],
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"src/app.js"},
Bundle: true,
Conditions: []string{"custom1", "custom2"},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#條件運作方式
條件讓您可以在不同的情況下將相同的匯入路徑重新導向到不同的檔案位置。包含條件和路徑的重新導向對應儲存在套件 package.json
檔案中的 exports
欄位。例如,這會使用 import
和 require
條件將 require('pkg/foo')
重新對應到 pkg/required.cjs
,並將 import 'pkg/foo'
重新對應到 pkg/imported.mjs
{
"name": "pkg",
"exports": {
"./foo": {
"import": "./imported.mjs",
"require": "./required.cjs",
"default": "./fallback.js"
}
}
}
條件會按照它們在 JSON 檔案中出現的順序進行檢查。因此,上述範例的行為類似於這樣
if (importPath === './foo') {
if (conditions.has('import')) return './imported.mjs'
if (conditions.has('require')) return './required.cjs'
return './fallback.js'
}
預設情況下,esbuild 內建五個具有特殊行為的條件,且無法停用
default
此條件始終處於活動狀態。它預計會排在最後,讓您可以在沒有其他條件套用的情況下提供備援。當您在 node 中原生執行程式碼時,此條件也會處於活動狀態。
import
此條件僅在匯入路徑來自 ESM
import
陳述式或import()
表達式時才會處於活動狀態。它可用於提供 ESM 特定的程式碼。當您在 node 中原生執行程式碼時,此條件也會處於活動狀態(但僅在 ESM 環境中)。require
此條件僅在匯入路徑來自 CommonJS
require()
呼叫時才會處於活動狀態。它可用於提供 CommonJS 特定的程式碼。當您在 node 中原生執行程式碼時,此條件也會處於活動狀態(但僅在 CommonJS 環境中)。browser
此條件僅在 esbuild 的 platform 設定設為
browser
時才會處於活動狀態。它可用於提供瀏覽器特定的程式碼。當您在 node 中原生執行程式碼時,此條件不會處於活動狀態。node
此條件僅在 esbuild 的 platform 設定設為
node
時才會處於活動狀態。它可用於提供 node 特定的程式碼。當您在 node 中原生執行程式碼時,此條件也會處於活動狀態。
當 platform 設為 browser
或 node
且未設定任何自訂條件時,也會自動包含以下條件。如果設定了任何自訂條件(即使是空清單),此條件將不再自動包含
模組
此條件可用於告知 esbuild 在匯入路徑中選擇 ESM 變體,以便在打包時提供更好的樹狀搖晃。當您在 node 中原生執行程式碼時,此條件不會啟動。它是特定於打包器,且源自 Webpack。
請注意,當您使用 require
和 import
條件時,您的套件可能會多次出現在套件中!這是一個微妙的問題,除了會讓產生的套件過大之外,還會因為程式碼狀態的重複副本而導致錯誤。這通常稱為 雙重套件風險。
避免雙重套件風險的一種方法,適用於打包器和在 node 中原生執行,是將所有程式碼放入 require
條件中,作為 CommonJS,並讓 import
條件只是一個輕量的 ESM 封裝,它會對您的套件呼叫 require
並使用 ESM 語法重新匯出套件。然而,此方法無法提供良好的樹狀搖晃,因為 esbuild 不會對 CommonJS 模組進行樹狀搖晃。
避免雙重套件風險的另一種方法是使用特定於打包器的 module
條件,以指示打包器始終載入套件的 ESM 版本,同時讓 node 始終回退到套件的 CommonJS 版本。import
和 module
都用於 ESM,但與 import
不同,即使使用 require
呼叫載入匯入路徑,module
條件也會始終啟動。這對於打包器來說效果很好,因為打包器支援使用 require
載入 ESM,但這不是 node 可以做到的,因為 node 故意不實作使用 require
載入 ESM。
#外部
支援:建立
您可以將檔案或套件標記為外部,以將其從您的建置中排除。匯入將被保留(對 iife
和 cjs
格式使用 require
,對 esm
格式使用 import
),而不是被打包,並將在執行時進行評估。
這有幾個用途。首先,它可用於修剪套件中不必要的程式碼,以取得您知道永遠不會執行的程式碼路徑。例如,套件可能包含僅在 node 中執行的程式碼,但您只會在瀏覽器中使用該套件。它還可用於在執行時從無法打包的套件中匯入 node 中的程式碼。例如,fsevents
套件包含一個原生擴充功能,而 esbuild 不支援。將某個東西標記為外部如下所示
echo 'require("fsevents")' > app.js
esbuild app.js --bundle --external:fsevents --platform=node
// app.js
require("fsevents");
import * as esbuild from 'esbuild'
import fs from 'node:fs'
fs.writeFileSync('app.js', 'require("fsevents")')
await esbuild.build({
entryPoints: ['app.js'],
outfile: 'out.js',
bundle: true,
platform: 'node',
external: ['fsevents'],
})
package main
import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
ioutil.WriteFile("app.js", []byte("require(\"fsevents\")"), 0644)
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Outfile: "out.js",
Bundle: true,
Write: true,
Platform: api.PlatformNode,
External: []string{"fsevents"},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
您還可以在外部路徑中使用 *
萬用字元,將與該模式相符的所有檔案標記為外部。例如,您可以使用 *.png
移除所有 .png
檔案,或使用 /images/*
移除所有從 /images/
開始的路徑
esbuild app.js --bundle "--external:*.png" "--external:/images/*"
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
outfile: 'out.js',
bundle: true,
external: ['*.png', '/images/*'],
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Outfile: "out.js",
Bundle: true,
Write: true,
External: []string{"*.png", "/images/*"},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
外部路徑會在路徑解析前後套用,讓您可以比對原始碼中的匯入路徑和絕對檔案系統路徑。如果外部路徑在任一情況下都符合,則該路徑會被視為外部。具體行為如下
在路徑解析開始之前,匯入路徑會與所有外部路徑進行比對。此外,如果外部路徑看起來像套件路徑(例如,不以
/
或./
或../
開頭),則會檢查匯入路徑是否具有該套件路徑作為路徑前綴。這表示
--external:
隱含也表示@foo/ bar --external:
,這會比對匯入路徑@foo/ bar/* @foo/
。因此,它也會將bar/ baz @foo/bar
套件內的所有路徑標記為外部。在路徑解析結束後,已解析的絕對路徑會與所有看起來不像套件路徑的外部路徑進行比對(例如,以
/
或./
或../
開頭)。但在比對之前,外部路徑會與目前工作目錄結合,然後正規化,成為絕對路徑(即使它包含*
萬用字元)。這表示您可以使用
--external:
將./dir/* dir
目錄中的所有內容標記為外部。請注意,開頭的./
很重要。相反地,使用--external:
會被視為套件路徑,且不會在路徑解析結束後進行檢查。dir/*
#主要欄位
支援:建立
當您在 node 中匯入套件時,該套件 package.json
檔案中的 main
欄位會決定要匯入哪個檔案(以及 許多其他規則)。包含 esbuild 在內的主要 JavaScript 捆綁器讓您可以在解析套件時指定其他要嘗試的 package.json
欄位。至少有三個此類欄位普遍使用
main
這是 所有旨在與 node 搭配使用的套件的標準欄位。
main
名稱已硬編碼到 node 的模組解析邏輯本身。由於它旨在與 node 搭配使用,因此可以合理預期此欄位中的檔案路徑是 CommonJS 式模組。模組
此欄位來自 一項提案,說明如何將 ECMAScript 模組整合到 node 中。因此,可以合理預期此欄位中的檔案路徑是 ECMAScript 式模組。此提案未被 node 採用(node 改用
"type":
),但被主要捆綁器採用,因為 ECMAScript 式模組可以產生更好的 tree shaking,或移除無用程式碼。"module" 對於套件作者:有些套件不正確地將
module
欄位用於瀏覽器特定程式碼,並將 node 特定程式碼留給main
欄位。這可能是因為 node 會忽略module
欄位,而人們通常只將捆綁器用於瀏覽器特定程式碼。然而,捆綁 node 特定程式碼也很有價值(例如,它可以減少下載和開機時間),而將瀏覽器特定程式碼放入module
的套件會阻止捆綁器有效執行 tree shaking。如果您嘗試在套件中發布瀏覽器特定程式碼,請改用browser
欄位。browser
此欄位來自 一項提案,允許捆綁器將 node 特定檔案或模組替換為其瀏覽器友善版本。它讓您可以指定備用的瀏覽器特定進入點。請注意,套件有可能同時使用
browser
和module
欄位(請見下方的備註)。
預設主欄位取決於目前的 平台 設定。這些預設值應與現有的套件生態系統最相容。但如果你願意,可以像這樣自訂它們
esbuild app.js --bundle --main-fields=module,main
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
mainFields: ['module', 'main'],
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
MainFields: []string{"module", "main"},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#套件作者
如果你想要撰寫一個套件,在其中結合使用 browser
欄位和 module
欄位,那麼你可能想要填寫完整 CommonJS 與 ESM 和瀏覽器與節點相容性矩陣中的所有四個項目。為此,你需要使用 browser
欄位的擴充形式,它是一個映射,而不仅仅是一個字串
{
"main": "./node-cjs.js",
"module": "./node-esm.js",
"browser": {
"./node-cjs.js": "./browser-cjs.js",
"./node-esm.js": "./browser-esm.js"
}
}
預期 main
欄位是 CommonJS,而 module
欄位預期是 ESM。關於要使用哪個模組格式的決定,與是否使用瀏覽器特定變體或節點特定變體的決定無關。如果你省略這四個項目中的任何一個,則有風險會選擇錯誤的變體。例如,如果你省略 CommonJS 瀏覽器建置的項目,則可能會選擇 CommonJS 節點建置。
請注意,使用 main
、module
和 browser
是舊方法。還有一個較新的方法可以執行此操作,你可能更喜歡使用它:package.json
中的 exports
欄位。它提供了不同的權衡。例如,它讓你對套件中所有子路徑的匯入有更精確的控制(而 main
欄位只讓你控制進入點),但它可能會導致你的套件根據你設定的方式被匯入多次。
#節點路徑
支援:建立
節點的模組解析演算法支援一個名為 NODE_PATH
的環境變數,其中包含解析匯入路徑時要使用的全域目錄清單。除了所有父目錄中的 node_modules
目錄之外,還會在這些路徑中搜尋套件。你可以使用 CLI 中的環境變數和 JS 和 Go API 中的陣列,將此目錄清單傳遞給 esbuild
NODE_PATH=someDir esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
nodePaths: ['someDir'],
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
NodePaths: []string{"someDir"},
EntryPoints: []string{"app.js"},
Bundle: true,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
如果你正在使用 CLI,並且想要使用 NODE_PATH
傳遞多個目錄,則必須在 Unix 上使用 :
分隔它們,在 Windows 上使用 ;
分隔它們。這是節點本身使用的相同格式。
#套件
支援:建立
使用此設定可將所有套件的相依性從套件中排除。這在 為 Node.js 套件 時很有用,因為許多 npm 套件使用 esbuild 在套件時不支援的 Node.js 特定功能(例如 __dirname
、import.meta.url
、fs.readFileSync
和 *.node
原生二進制模組)。使用方式如下
esbuild app.js --bundle --packages=external
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
packages: 'external',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Packages: api.PackagesExternal,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
啟用此設定會自動將看起來像 npm 套件的所有匯入路徑(即不以 .
或 ..
路徑元件開頭且不是絕對路徑)標記為外部。它與手動將每個相依性傳遞給 external 的效果相同,但更簡潔。如果您想要自訂哪些相依性是外部的,哪些不是,則應使用 external,而不是此設定。
請注意,此設定僅在啟用 套件 時才有作用。此外,請注意,在匯入路徑被任何已設定的 別名 改寫後,才會將匯入路徑標記為外部,因此當使用此設定時,別名功能仍然有效。
#保留符號連結
支援:建立
此設定反映 Node.js 中的 --preserve-symlinks
設定。如果您使用該設定(或 Webpack 中類似的 resolve.symlinks
設定),您可能也需要在 esbuild 中啟用此設定。可以這樣啟用
esbuild app.js --bundle --preserve-symlinks --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
preserveSymlinks: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
PreserveSymlinks: true,
Outfile: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
啟用此設定會導致 esbuild 透過原始檔案路徑(即未追蹤符號連結的路徑)而不是實際檔案路徑(即追蹤符號連結後的路徑)來確定檔案身分。這在某些目錄結構中可能很有用。請記住,這表示如果有多個符號連結指向一個檔案,則該檔案可能會被賦予多個身分,這可能會導致它在產生的輸出檔案中出現多次。
注意:「符號連結」一詞是指 符號連結,是指檔案系統中路徑可以重新導向到另一個路徑的功能。
#解析副檔名
支援:建立
node 使用的 解析演算法 支援隱含的檔案副檔名。您可以使用 require(
,它會依序檢查 ./file
、./file.js
、./file.json
和 ./file.node
。包含 esbuild 在內的現代套件管理工具也將此概念延伸到其他檔案類型。esbuild 中隱含檔案副檔名的完整順序可以使用 resolve extensions 設定進行自訂,其預設值為 .tsx,
esbuild app.js --bundle --resolve-extensions=.ts,.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
resolveExtensions: ['.ts', '.js'],
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
ResolveExtensions: []string{".ts", ".js"},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
請注意,esbuild 故意不將新的 .mjs
和 .cjs
副檔名包含在此清單中。Node 的解析演算法不會將這些副檔名視為隱含檔案副檔名,因此 esbuild 也不會。如果您想匯入具有這些副檔名的檔案,您應該在匯入路徑中明確加入副檔名,或變更此設定以包含您想要隱含的其他副檔名。
#工作目錄
支援:建立
此 API 選項讓您可以指定要使用於建置的工作目錄。它通常預設為您用來呼叫 esbuild API 的程序的目前 工作目錄。esbuild 會將工作目錄用於多項不同的用途,包括將作為 API 選項提供的相對路徑解析為絕對路徑,以及在記錄訊息中將絕對路徑美化列印為相對路徑。以下是自訂 esbuild 工作目錄的方法
cd "/var/tmp/custom/working/directory"
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['file.js'],
absWorkingDir: '/var/tmp/custom/working/directory',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"file.js"},
AbsWorkingDir: "/var/tmp/custom/working/directory",
Outfile: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
注意:如果您正在使用 Yarn Plug'n'Play,請記住此工作目錄用於搜尋 Yarn 的清單檔案。如果您從不相關的目錄執行 esbuild,您必須將此工作目錄設定為包含清單檔案的目錄(或其子目錄),才能讓 esbuild 找到清單檔案。
#轉換
#JSX
此選項會告訴 esbuild 如何處理 JSX 語法。以下是可用的選項
transform
這會告訴 esbuild 使用通用轉換將 JSX 轉換為 JS,此轉換會在許多使用 JSX 語法的函式庫之間共用。每個 JSX 元素都會轉換成呼叫 JSX 工廠 函式,其中元素的元件(或片段的 JSX 片段)作為第一個引數。第二個引數是一個屬性陣列(或如果沒有屬性,則為
null
)。任何存在的子元素都會在第二個引數之後成為其他引數。如果您想針對每個檔案設定此設定,您可以使用
// @jsxRuntime
註解來執行此操作。這是 esbuild 遵循的 Babel 的 JSX 外掛程式 的慣例。classic 保留
這會保留輸出中的 JSX 語法,而不是將其轉換為函式呼叫。JSX 元素被視為一級語法,並且仍會受到其他設定的影響,例如 縮小 和 屬性混淆。
請注意,這表示輸出檔案不再是有效的 JavaScript 程式碼。此功能旨在於在打包後,使用其他工具轉換 esbuild 輸出檔案中的 JSX 語法時使用。
自動
此轉換已 在 React 17+ 中引入,並且非常特定於 React。它會自動從 JSX 匯入來源 產生
import
陳述式,並針對語法處理方式引入許多特殊情況。細節過於複雜,無法在此說明。如需更多資訊,請閱讀 React 關於其新 JSX 轉換的說明文件。如果您要啟用此轉換的開發模式版本,您需要另外啟用 JSX dev 設定。如果您要根據每個檔案設定此設定,您可以使用
// @jsxRuntime
註解。這是來自 Babel 的 JSX 外掛程式 的慣例,esbuild 會遵循此慣例。automatic
以下是如何將 JSX 轉換設定為 preserve
的範例
echo '<div/>' | esbuild --jsx=preserve --loader=jsx
<div />;
import * as esbuild from 'esbuild'
let result = await esbuild.transform('<div/>', {
jsx: 'preserve',
loader: 'jsx',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
result := api.Transform("<div/>", api.TransformOptions{
JSX: api.JSXPreserve,
Loader: api.LoaderJSX,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#JSX dev
如果 JSX 轉換已設定為 automatic
,啟用此設定會導致 esbuild 自動將檔案名稱和來源位置注入到每個 JSX 元素中。您的 JSX 函式庫可以使用此資訊來協助除錯。如果 JSX 轉換已設定為 automatic
以外的設定,此設定不會執行任何動作。以下是如何啟用此設定的範例
echo '<a/>' | esbuild --loader=jsx --jsx=automatic
import { jsx } from "react/jsx-runtime";
/* @__PURE__ */ jsx("a", {});
echo '<a/>' | esbuild --loader=jsx --jsx=automatic --jsx-dev
import { jsxDEV } from "react/jsx-dev-runtime";
/* @__PURE__ */ jsxDEV("a", {}, void 0, false, {
fileName: "<stdin>",
lineNumber: 1,
columnNumber: 1
}, this);
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.jsx'],
jsxDev: true,
jsx: 'automatic',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.jsx"},
JSXDev: true,
JSX: api.JSXAutomatic,
Outfile: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#JSX 工廠
這會設定針對每個 JSX 元素呼叫的函式。一般而言,此類 JSX 表達式
<div>Example text</div>
會編譯成類似這樣呼叫 React.createElement
的函式
React.createElement("div", null, "Example text");
您可以透過變更 JSX 工廠來呼叫 React.createElement
以外的函式。例如,改為呼叫函式 h
(由其他函式庫使用,例如 Preact)
echo '<div/>' | esbuild --jsx-factory=h --loader=jsx
/* @__PURE__ */ h("div", null);
import * as esbuild from 'esbuild'
let result = await esbuild.transform('<div/>', {
jsxFactory: 'h',
loader: 'jsx',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
result := api.Transform("<div/>", api.TransformOptions{
JSXFactory: "h",
Loader: api.LoaderJSX,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
或者,如果您使用 TypeScript,您可以透過將以下內容新增到 tsconfig.json
檔案中來設定 TypeScript 的 JSX,esbuild 應該會自動擷取它,而不需要設定
{
"compilerOptions": {
"jsxFactory": "h"
}
}
如果您要根據每個檔案設定此設定,您可以使用 // @jsx
註解。請注意,當 JSX 轉換已設定為 automatic
時,此設定不適用。
#JSX 片段
這會設定呼叫每個 JSX 片段的函式。一般來說,像這樣的 JSX 片段表達式
<>Stuff</>
會編譯成使用 React.Fragment
元件,如下所示
React.createElement(React.Fragment, null, "Stuff");
您可以透過變更 JSX 片段來使用 React.Fragment
以外的元件。例如,改用 Fragment
元件(其他函式庫使用,例如 Preact)
echo '<>x</>' | esbuild --jsx-fragment=Fragment --loader=jsx
/* @__PURE__ */ React.createElement(Fragment, null, "x");
import * as esbuild from 'esbuild'
let result = await esbuild.transform('<>x</>', {
jsxFragment: 'Fragment',
loader: 'jsx',
})
console.log(result.code)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
result := api.Transform("<>x</>", api.TransformOptions{
JSXFragment: "Fragment",
Loader: api.LoaderJSX,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
或者,如果您使用 TypeScript,您可以透過將以下內容新增到 tsconfig.json
檔案中來設定 TypeScript 的 JSX,esbuild 應該會自動擷取它,而不需要設定
{
"compilerOptions": {
"jsxFragmentFactory": "Fragment"
}
}
如果您想針對每個檔案設定這個設定,您可以使用 // @jsxFrag
註解來執行。請注意,當 JSX 轉換設定為 automatic
時,這個設定不會套用。
#JSX 匯入來源
如果 JSX 轉換已設定為 automatic
,設定這個設定可讓您變更 esbuild 用來自動匯入其 JSX 輔助函式的函式庫。請注意,這只適用於 特定於 React 17+ 的 JSX 轉換。如果您將 JSX 匯入來源設定為 your-pkg
,則該套件必須至少公開下列匯出
import { createElement } from "your-pkg"
import { Fragment, jsx, jsxs } from "your-pkg/jsx-runtime"
import { Fragment, jsxDEV } from "your-pkg/jsx-dev-runtime"
/jsx-runtime
和 /jsx-dev-runtime
子路徑是根據設計硬編碼,無法變更。jsx
和 jsxs
匯入會在 JSX 開發模式 關閉時使用,而 jsxDEV
匯入會在 JSX 開發模式開啟時使用。這些的意義在 React 有關其新 JSX 轉換的說明文件 中有說明。createElement
匯入會在元素有屬性散佈接著是 key
屬性時使用,如下所示
return <div {...props} key={key} />
以下是將 JSX 匯入來源設定為 preact
的範例
esbuild app.jsx --jsx-import-source=preact --jsx=automatic
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.jsx'],
jsxImportSource: 'preact',
jsx: 'automatic',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.jsx"},
JSXImportSource: "preact",
JSX: api.JSXAutomatic,
Outfile: "out.js",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
或者,如果您使用 TypeScript,您可以透過將這個內容新增到 tsconfig.json
檔案中來設定 TypeScript 的 JSX 匯入來源,而 esbuild 應該會自動選取它,無需設定
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
如果您想針對每個檔案控制這個設定,您可以使用每個檔案中的 // @jsxImportSource
註解來執行。您可能也需要新增 // @jsxRuntime
註解,如果 JSX 轉換尚未透過其他方式設定,或者您也想要針對每個檔案設定它。
#JSX 副作用
預設情況下,esbuild 會假設 JSX 表達式沒有副作用,這表示它們會加上 /* @__PURE__ */
註解,並在未使用的狀況下於打包時移除。這遵循了 JSX 用於虛擬 DOM 的常見用法,並適用於絕大多數 JSX 函式庫。不過,有些人撰寫了沒有這個屬性的 JSX 函式庫(特別是 JSX 表達式可能會有任意的副作用,且無法在未使用的狀況下移除)。如果你使用的是此類函式庫,你可以使用這個設定告訴 esbuild,JSX 表達式有副作用
esbuild app.jsx --jsx-side-effects
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.jsx'],
outfile: 'out.js',
jsxSideEffects: true,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.jsx"},
Outfile: "out.js",
JSXSideEffects: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#支援
這個設定讓你可以在個別語法功能層級自訂 esbuild 不支援的語法功能集。例如,你可以使用這個設定告訴 esbuild,BigInt 不受支援,這樣當你嘗試使用 BigInt 時,esbuild 會產生錯誤。通常,當你使用 target
設定時,會為你設定這個設定,你通常應該使用這個設定,而不是這個設定。如果除了這個設定之外,還指定了目標,這個設定會覆寫目標所指定的任何設定。
以下是說明為什麼你可能想要使用這個設定,而不是設定目標,或除了設定目標之外,還使用這個設定的一些範例
JavaScript 執行環境通常會快速實作較新的語法功能,而這些語法功能比等效的舊版 JavaScript 慢,你可以透過告訴 esbuild 假裝這個語法功能不受支援,來加快速度。例如,V8 有個 關於物件擴充的長久效能錯誤,你可以透過手動複製屬性,而不是使用物件擴充語法,來避免這個錯誤。
除了 esbuild 的
target
設定所辨識的 JavaScript 實作之外,還有許多其他 JavaScript 實作,而這些實作可能不支援某些功能。如果你鎖定的是此類實作,你可以使用這個設定,使用自訂語法功能相容性集來設定 esbuild,而不需要變更 esbuild 本身。例如,TypeScript 的 JavaScript 剖析器可能不支援 任意的模組命名空間識別名稱,因此你可能想要在鎖定 TypeScript 的 JavaScript 剖析器時關閉這些功能。你可能正在使用其他工具處理 esbuild 的輸出,你可能想要 esbuild 轉換某些功能,而其他工具轉換其他某些功能。例如,如果你使用 esbuild 將檔案個別轉換為 ES5,但你接著將輸出提供給 Webpack 進行打包,你可能想要保留
import()
表達式,即使它們在 ES5 中是語法錯誤。
如果你希望 esbuild 將某個語法功能視為不受支援,你可以這樣指定
esbuild app.js --supported:bigint=false
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
supported: {
'bigint': false,
},
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Supported: map[string]bool{
"bigint": false,
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
語法功能使用 esbuild 特有的功能名稱指定。完整的 feature names 如下
JavaScript
arbitrary-module-namespace-names
array-spread
arrow
async-await
async-generator
bigint
class
class-field
class-private-accessor
class-private-brand-check
class-private-field
class-private-method
class-private-static-accessor
class-private-static-field
class-private-static-method
class-static-blocks
class-static-field
const-and-let
decorators
default-argument
destructuring
dynamic-import
exponent-operator
export-star-as
for-await
for-of
function-or-class-property-access
generator
hashbang
import-assertions
import-meta
inline-script
logical-assignment
nested-rest-binding
new-target
node-colon-prefix-import
node-colon-prefix-require
nullish-coalescing
object-accessors
object-extensions
object-rest-spread
optional-catch-binding
optional-chain
regexp-dot-all-flag
regexp-lookbehind-assertions
regexp-match-indices
regexp-named-capture-groups
regexp-set-notation
regexp-sticky-and-unicode-flags
regexp-unicode-property-escapes
rest-argument
template-literal
top-level-await
typeof-exotic-object-is-object
unicode-escapes
using
CSS
color-functions
gradient-double-position
gradient-interpolation
gradient-midpoints
hwb
hex-rgba
inline-style
inset-property
is-pseudo-class
modern-rgb-hsl
nesting
rebecca-purple
#目標
這會設定產生 JavaScript 和/或 CSS 程式碼的目標環境。它會告訴 esbuild 將對這些環境來說太新的 JavaScript 語法轉換成舊版的 JavaScript 語法,讓這些環境可以使用。例如,??
這個運算子是在 Chrome 80 中推出的,因此 esbuild 在目標為 Chrome 79 或更早版本時,會將它轉換成等效的(但更冗長的)條件式運算式。
請注意,這僅與語法功能有關,不與 API 有關。它不會自動為這些環境未使用的新的 API 新增 多重載入。您必須明確匯入您需要的 API 的多重載入(例如,透過匯入 core-js
)。自動多重載入注入不在 esbuild 的範圍內。
每個目標環境都是環境名稱,後接版本號碼。目前支援下列環境名稱
chrome
deno
edge
firefox
hermes
ie
ios
node
opera
rhino
safari
此外,您也可以指定 JavaScript 語言版本,例如 es2020
。預設目標是 esnext
,這表示預設情況下,esbuild 會假設支援所有最新的 JavaScript 和 CSS 功能。以下是設定多個目標環境的範例。您不需要指定所有目標環境;您只要指定專案關心的目標環境子集即可。您也可以更精確地指定版本號碼(例如,node12.19.0
,而非僅 node12
)
esbuild app.js --target=es2020,chrome58,edge16,firefox57,node12,safari11
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
target: [
'es2020',
'chrome58',
'edge16',
'firefox57',
'node12',
'safari11',
],
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Target: api.ES2020,
Engines: []api.Engine{
{Name: api.EngineChrome, Version: "58"},
{Name: api.EngineEdge, Version: "16"},
{Name: api.EngineFirefox, Version: "57"},
{Name: api.EngineNode, Version: "12"},
{Name: api.EngineSafari, Version: "11"},
},
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
您可以參閱 JavaScript 載入器,以了解哪些語法功能與哪些語言版本一起推出。請記住,儘管 JavaScript 語言版本(例如 es2020
)會以年份來識別,但那是規範通過的年份。這與所有主要瀏覽器實作該規範的年份無關,後者通常會早於或晚於該年份。
如果您使用 esbuild 尚未支援轉換為您目前的語言目標的語法功能,esbuild 會在使用不支援的語法的地方產生錯誤。例如,當目標是 es5
語言版本時,通常會發生這種情況,因為 esbuild 僅支援將大多數較新的 JavaScript 語法功能轉換為 es6
。
如果您需要自訂支援的語法功能集,針對個別功能層級,除了或取代 target
提供的內容,您可以使用 supported
設定來執行此操作。
#最佳化
#定義
此功能提供一種方式,可以用常數表達式取代全域識別碼。這是一種在不變更程式碼本身的情況下,在不同建置之間變更部分程式碼行為的方式
echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=true
hooks = require("hooks");
echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=false
hooks = false;
import * as esbuild from 'esbuild'let js = 'hooks = DEBUG && require("hooks")'(await esbuild.transform(js, {
define: { DEBUG: 'true' },
})).code
'hooks = require("hooks");\n'
(await esbuild.transform(js, {
define: { DEBUG: 'false' },
})).code
'hooks = false;\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "hooks = DEBUG && require('hooks')"
result1 := api.Transform(js, api.TransformOptions{
Define: map[string]string{"DEBUG": "true"},
})
if len(result1.Errors) == 0 {
fmt.Printf("%s", result1.Code)
}
result2 := api.Transform(js, api.TransformOptions{
Define: map[string]string{"DEBUG": "false"},
})
if len(result2.Errors) == 0 {
fmt.Printf("%s", result2.Code)
}
}
每個 define
項目將識別碼對應到包含表達式的程式碼字串。字串中的表達式必須為 JSON 物件(null、布林值、數字、字串、陣列或物件)或單一識別碼。陣列和物件以外的替換表達式會內嵌替換,表示它們可以參與常數摺疊。陣列和物件替換表達式會儲存在變數中,然後使用識別碼來參照,而不是內嵌替換,這樣可以避免替換重複的值,但表示這些值不會參與常數摺疊。
如果您想用字串常數替換某個項目,請記住傳遞給 esbuild 的替換值本身必須包含引號,因為每個 define
項目都對應到包含程式碼的字串。省略引號表示替換值是識別碼。以下範例會加以說明
echo 'id, str' | esbuild --define:id=text --define:str=\"text\"
text, "text";
import * as esbuild from 'esbuild'(await esbuild.transform('id, str', {
define: { id: 'text', str: '"text"' },
})).code
'text, "text";\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
result := api.Transform("id, text", api.TransformOptions{
Define: map[string]string{
"id": "text",
"str": "\"text\"",
},
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
如果您使用 CLI,請記住不同的殼層有不同的規則來跳脫雙引號字元(當替換值是字串時需要)。請使用 \"
反斜線跳脫,因為它在 bash 和 Windows 命令提示字元中都能運作。其他在 bash 中運作的雙引號跳脫方法,例如用單引號將其包圍,在 Windows 中將無法運作,因為 Windows 命令提示字元不會移除單引號。這與從 package.json
檔案中的 npm 腳本使用 CLI 有關,人們會希望它能在所有平台上運作
{
"scripts": {
"build": "esbuild --define:process.env.NODE_ENV=\\\"production\\\" app.js"
}
}
如果您在使用不同的殼層時仍遇到跨平台引號跳脫問題,您可能會想要改用 JavaScript API。您可以在其中使用正規的 JavaScript 語法來消除跨平台差異。
如果您正在尋找更進階的 define 功能,可以將表達式替換為非常數的項目(例如,將全域變數替換為 shim),您可能會可以使用類似的 inject 功能來執行此操作。
#刪除
這會指示 esbuild 在建置前編輯您的原始碼,以刪除某些建構。目前有兩項可能被刪除的項目
偵錯器
傳遞此旗標會導致所有
debugger
陳述式 從輸出中移除。這類似於熱門的 UglifyJS 和 Terser JavaScript 壓縮器中提供的drop_debugger: true
旗標。JavaScript 的
debugger
陳述式會使主動偵錯器將該陳述式視為自動設定的斷點。當偵錯器開啟時,包含此陳述式的程式碼會自動暫停。如果沒有開啟偵錯器,則該陳述式不會執行任何動作。從程式碼中移除這些陳述式只是會防止偵錯器在程式碼執行時自動停止。您可以像這樣移除
debugger
陳述式
esbuild app.js --drop:debugger
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
drop: ['debugger'],
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Drop: api.DropDebugger,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
主控台
傳遞此標記會導致所有
主控台
API 呼叫 從輸出中移除。這類似於熱門的 UglifyJS 和 Terser JavaScript 壓縮器中提供的drop_console: true
標記。警告:使用此標記可能會在您的程式碼中產生錯誤!此標記會移除整個呼叫表達式,包括所有呼叫引數。如果這些引數中有任何具有重要的副作用,使用此標記會改變您的程式碼行為。使用此標記時請務必小心。
如果您想移除主控台 API 呼叫,但不移除具有副作用的引數(因此您不會產生錯誤),您應該將相關的 API 呼叫標記為 純淨。例如,您可以使用
--pure:
將console.log console.log
標記為純淨。當啟用壓縮時,這將導致這些 API 呼叫被安全地移除。您可以像這樣移除
主控台
API 呼叫
esbuild app.js --drop:console
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
drop: ['console'],
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Drop: api.DropConsole,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#移除標籤
這會指示 esbuild 在建置之前編輯您的原始程式碼,以移除具有特定標籤名稱的 標籤陳述式。例如,考慮以下程式碼
function example() {
DEV: doAnExpensiveCheck()
return normalCodePath()
}
如果您使用此選項移除所有名為 DEV
的標籤,則 esbuild 會提供以下結果
function example() {
return normalCodePath();
}
您可以像這樣設定此功能(這將移除 DEV
和 TEST
標籤)
esbuild app.js --drop-labels=DEV,TEST
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
dropLabels: ['DEV', 'TEST'],
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
DropLabels: []string{"DEV", "TEST"},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
請注意,這不是移除程式碼的唯一方法。另一種更常見的方法是使用 定義 功能,以布林值取代特定的全域變數。例如,考慮以下程式碼
function example() {
DEV && doAnExpensiveCheck()
return normalCodePath()
}
如果您將 DEV
定義為 false
,則 esbuild 會提供以下結果
function example() {
return normalCodePath();
}
這與使用標籤幾乎相同。然而,使用標籤而不是全域變數來有條件地移除程式碼的優點是,您不必擔心全域變數未定義,因為有人忘記設定 esbuild 以將其替換為其他東西。使用標籤方法的一些缺點是,當標籤未移除時,有條件地移除程式碼會變得難以閱讀,而且它不適用於嵌入在巢狀表達式中的程式碼。對於特定專案使用哪種方法取決於個人偏好。
#忽略註解
由於 JavaScript 是一種動態語言,編譯器有時很難辨識未使用的程式碼,因此社群開發了特定註解,以協助編譯器了解哪些程式碼應視為沒有副作用,且可以移除。目前 esbuild 支援兩種形式的副作用註解
函式呼叫前的內嵌註解
/* @__PURE__ */
會告知 esbuild,如果未使用的結果值,則可以移除函式呼叫。請參閱 pure API 選項,以取得更多資訊。package.json
中的sideEffects
欄位可用於告知 esbuild,如果該檔案的所有匯入最終未被使用,則可以移除套件中的哪些檔案。這是 Webpack 的慣例,許多發佈到 npm 的函式庫在套件定義中已包含此欄位。您可以在 Webpack 的文件 中深入了解此欄位。
這些註解可能會造成問題,因為編譯器完全依賴開發人員的準確性,而開發人員偶爾會發佈註解不正確的套件。對於開發人員來說,sideEffects
欄位特別容易出錯,因為預設情況下,如果沒有使用任何匯入,則會將套件中的所有檔案視為無效程式碼。如果您新增一個包含副作用的新檔案,卻忘記更新該欄位,則當人們嘗試將其打包時,您的套件可能會中斷。
這就是為什麼 esbuild 包含忽略副作用註解的方法。只有在遇到問題時,才應啟用此功能,因為必要的程式碼意外從套件中移除,導致套件中斷
esbuild app.js --bundle --ignore-annotations
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
ignoreAnnotations: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
IgnoreAnnotations: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
啟用此功能表示 esbuild 將不再遵循 /* @__PURE__ */
註解或 sideEffects
欄位。不過,它仍會自動 tree shaking 未使用的匯入,因為這不依賴開發人員的註解。理想情況下,此標記僅為暫時解決方案。您應將這些問題回報給套件維護者,以進行修復,因為它們表示套件有問題,並且也可能會讓其他人絆倒。
#注入
支援:建立
此選項允許您自動使用來自另一個檔案的匯入取代全域變數。這對於將您無法控制的程式碼調整到新環境來說,可能是一個有用的工具。例如,假設您有一個名為 process-cwd-shim.js
的檔案,它使用匯出名稱 process.cwd
匯出一個 shim
// process-cwd-shim.js
let processCwdShim = () => ''
export { processCwdShim as 'process.cwd' }
// entry.js
console.log(process.cwd())
這旨在取代對節點 process.cwd()
函式的使用,以防止呼叫它的套件在瀏覽器中執行時發生崩潰。您可以使用注入功能,將對全域屬性 process.cwd
的所有參照替換為從該檔案匯入的內容
esbuild entry.js --inject:./process-cwd-shim.js --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['entry.js'],
inject: ['./process-cwd-shim.js'],
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"entry.js"},
Inject: []string{"./process-cwd-shim.js"},
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
結果類似這樣
// out.js
var processCwdShim = () => "";
console.log(processCwdShim());
你可以將注入功能視為類似於 定義 功能,但它會用匯入檔案來取代表達式,而不是用常數,且要取代的表達式會使用檔案中的匯出名稱來指定,而不是使用 esbuild API 中的內嵌字串。
#自動匯入 JSX
React(最初建立 JSX 語法的函式庫)有一種稱為 automatic
的模式,在這種模式下,你不需要 import
任何東西就能使用 JSX 語法。JSX 轉 JS 轉譯器會自動為你匯入正確的 JSX 工廠函式。你可以使用 esbuild 的 jsx
設定來啟用 automatic
JSX 模式。如果你想要自動匯入 JSX,且你正在使用夠新的 React 版本,那麼你應該使用 automatic
JSX 模式。
然而,將 jsx
設定為 automatic
不幸地也表示你正在使用高度 React 特定的 JSX 轉譯,而不是預設的通用 JSX 轉譯。這表示撰寫 JSX 工廠函式會更複雜,也表示 automatic
模式無法與預期與標準 JSX 轉譯一起使用的函式庫搭配使用(包括舊版本的 React)。
當 JSX 轉譯未設定為 automatic
時,你可以使用 esbuild 的注入功能來自動匯入 JSX 表達式的 工廠 和 片段。以下是可注入來執行此動作的範例檔案
const { createElement, Fragment } = require('react')
export {
createElement as 'React.createElement',
Fragment as 'React.Fragment',
}
此程式碼使用 React 函式庫作為範例,但你也可以使用此方法搭配其他 JSX 函式庫,只要進行適當的變更即可。
#注入沒有匯入的檔案
你也可以將此功能與沒有匯出的檔案搭配使用。在這種情況下,注入的檔案會出現在其他輸出之前,就好像每個輸入檔案都包含 import
一樣。由於 ECMAScript 模組的工作方式,此注入仍然是「衛生的」,也就是說,不同檔案中具有相同名稱的符號會重新命名,因此它們不會彼此衝突。
#有條件地注入檔案
如果你想要有條件地僅在實際使用匯出時匯入檔案,你應該將注入的檔案標記為沒有副作用,方法是將它放入套件中,並在該套件的 package.json
檔案中加入 "sideEffects":
。此設定是 Webpack 的慣例,esbuild 尊重任何匯入的檔案,而不僅是與注入一起使用的檔案。
#保留名稱
在 JavaScript 中,函式和類別上的 name
屬性預設為原始碼中鄰近的識別碼。這些語法形式全部將函式的 name
屬性設定為 "fn"
function fn() {}
let fn = function() {};
fn = function() {};
let [fn = function() {}] = [];
let {fn = function() {}} = {};
[fn = function() {}] = [];
({fn = function() {}} = {});
然而,縮小會重新命名符號以縮小程式碼大小,而套件打包有時需要重新命名符號以避免衝突。這會變更許多此類情況的 name
屬性值。這通常沒問題,因為 name
屬性通常只用於偵錯。然而,有些架構依賴 name
屬性進行註冊和繫結。如果是這種情況,你可以啟用此選項,以保留縮小程式碼中的原始 name
值
esbuild app.js --minify --keep-names
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
minify: true,
keepNames: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
KeepNames: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
請注意,如果已將目標設定為不允許 esbuild 變更函式和類別上 name
屬性的舊環境,則此功能將不可用。這是因為這些環境不支援 ES6。
#混淆屬性
此設定讓你傳遞正規表示式給 esbuild,以告知 esbuild 自動重新命名所有符合此正規表示式的屬性。當你想要縮小程式碼中的某些屬性名稱以縮小產生的程式碼大小,或稍微混淆程式碼意圖時,這很有用。
以下是一個使用正規表示式 _$
來混淆所有以底線結尾的屬性的範例,例如 foo_
。這會將 print({
混淆成 print({
esbuild app.js --mangle-props=_$
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
mangleProps: /_$/,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
MangleProps: "_$",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
只混淆以底線結尾的屬性是一個合理的啟發法,因為一般的 JS 程式碼通常不包含此類識別碼。瀏覽器 API 也不使用此命名慣例,因此這也能避免與瀏覽器 API 發生衝突。如果你想要避免混淆例如 __defineGetter__
等名稱,你可以考慮使用更複雜的正規表示式,例如 [^_]_$
(即必須以非底線結尾,後接底線)。
這是一個獨立的設定,而不是 縮小 設定的一部分,因為它是一個不安全的轉換,無法用於任意的 JavaScript 程式碼。它僅在所提供的正規表示式與您想要混淆的所有屬性相符,且與您不想要混淆的任何屬性都不相符時才有效。它也僅在您在任何情況下都不間接地參照混淆的屬性時才有效。例如,這表示您不能使用 obj[prop]
來參照屬性,其中 prop
是包含屬性名稱的字串。特別是,以下語法結構是唯一符合屬性混淆條件的結構
語法 | 範例 |
---|---|
點屬性存取 | x.foo_ |
點選用鏈 | x?.foo_ |
物件屬性 | x = { foo_: y } |
物件方法 | x = { foo_() {} } |
類別欄位 | class x { foo_ = y } |
類別方法 | class x { foo_() {} } |
物件解構繫結 | let { foo_: x } = y |
物件解構指定 | ({ foo_: x } = y) |
JSX 元素成員表達式 | <X.foo_></X.foo_> |
JSX 屬性名稱 | <X foo_={y} /> |
TypeScript 命名空間匯出 | namespace x { export let foo_ = y } |
TypeScript 參數屬性 | class x { constructor(public foo_) {} } |
使用此功能時,請記住屬性名稱僅在單一的 esbuild API 呼叫中持續混淆,但不會跨越 esbuild API 呼叫。每個 esbuild API 呼叫都會執行獨立的屬性混淆操作,因此由兩個不同的 API 呼叫產生的輸出檔案可能會將同一個屬性混淆成兩個不同的名稱,這可能會導致產生的程式碼行為不正確。
#帶引號的屬性
預設情況下,esbuild 不會修改字串文字的內容。這表示您可以透過將個別屬性加上引號作為字串的方式,來避免屬性混淆。但是,您必須在所有地方持續對給定的屬性使用引號或不使用引號,才能讓此方法有效。例如,print({
會被混淆成 print({
,而 print({
則不會被混淆。
如果您希望 esbuild 也混淆字串文字的內容,您可以明確啟用此行為,如下所示
esbuild app.js --mangle-props=_$ --mangle-quoted
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
mangleProps: /_$/,
mangleQuoted: true,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
MangleProps: "_$",
MangleQuoted: api.MangleQuotedTrue,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
啟用此功能後,以下語法結構也符合屬性混淆的條件
語法 | 範例 |
---|---|
帶引號的屬性存取 | x['foo_'] |
帶引號的選用鏈 | x?.['foo_'] |
引號物件屬性 | x = { 'foo_': y } |
引號物件方法 | x = { 'foo_'() {} } |
引號類別欄位 | class x { 'foo_' = y } |
引號類別方法 | class x { 'foo_'() {} } |
引號物件解構繫結 | let { 'foo_': x } = y |
引號物件解構指派 | ({ 'foo_': x } = y) |
字串文字在 in 的左側 |
'foo_' in x |
#混淆其他字串
混淆 引號屬性 仍然只會混淆屬性名稱位置的字串。有時你可能還需要混淆程式碼中其他任意位置的字串中的屬性名稱。為此,你可以使用 /* @__KEY__ */
註解作為字串的前綴,告訴 esbuild 應將字串的內容視為可以混淆的屬性名稱。例如
let obj = {}
Object.defineProperty(
obj,
/* @__KEY__ */ 'foo_',
{ get: () => 123 },
)
console.log(obj.foo_)
這將導致字串 'foo_'
的內容作為屬性名稱混淆(假設 屬性混淆 已啟用且 foo_
符合重新命名的條件)。/* @__KEY__ */
註解是 Terser 的慣例,Terser 是一款具備類似屬性混淆功能的熱門 JavaScript 壓縮器。
#防止重新命名
如果你想將某些屬性排除在混淆之外,可以使用其他設定保留它們。例如,這使用正規表示式 ^__.*__$
保留所有以兩個底線開頭和結尾的屬性,例如 __foo__
esbuild app.js --mangle-props=_$ "--reserve-props=^__.*__$"
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
mangleProps: /_$/,
reserveProps: /^__.*__$/,
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
MangleProps: "_$",
ReserveProps: "^__.*__$",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#保留重新命名決策
屬性混淆功能的高階用法包括將從原始名稱到混淆名稱的對應儲存在永久快取中。啟用後,所有混淆的屬性重新命名都會在初始建置期間記錄在快取中。後續建置會重複使用儲存在快取中的重新命名,並為任何新增加的屬性新增其他重新命名。這會產生一些後果
你可以在將快取傳遞給 esbuild 之前自訂混淆屬性重新命名為哪些名稱。
快取用作混淆所有屬性的清單。你可以輕鬆掃描它,查看是否有任何意外的屬性重新命名。
你可以透過將重新命名的值設定為
false
(而不是字串)來停用個別屬性的混淆。這類似於 保留屬性 設定,但以每個屬性為基礎。您可以確保在組建之間進行一致的重新命名(例如,主執行緒檔案和網頁工作執行緒,或函式庫和外掛程式)。沒有此功能,每個組建都會執行獨立的重新命名操作,而混淆的屬性名稱可能不一致。
例如,考慮以下輸入檔案
console.log({
someProp_: 1,
customRenaming_: 2,
disabledRenaming_: 3
});
如果我們希望將 customRenaming_
重新命名為 cR_
,並且我們不希望重新命名 disabledRenaming_
,我們可以將以下混淆快取 JSON 傳遞給 esbuild
{
"customRenaming_": "cR_",
"disabledRenaming_": false
}
混淆快取 JSON 可以像這樣傳遞給 esbuild
esbuild app.js --mangle-props=_$ --mangle-cache=cache.json
import * as esbuild from 'esbuild'
let result = await esbuild.build({
entryPoints: ['app.js'],
mangleProps: /_$/,
mangleCache: {
customRenaming_: "cR_",
disabledRenaming_: false
},
})
console.log('updated mangle cache:', result.mangleCache)
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
MangleProps: "_$",
MangleCache: map[string]interface{}{
"customRenaming_": "cR_",
"disabledRenaming_": false,
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
fmt.Println("updated mangle cache:", result.MangleCache)
}
當啟用屬性命名時,將產生以下輸出檔案
console.log({
a: 1,
cR_: 2,
disabledRenaming_: 3
});
以及以下更新的混淆快取
{
"customRenaming_": "cR_",
"disabledRenaming_": false,
"someProp_": "a"
}
#縮小
啟用後,將縮小產生的程式碼,而不是以美化格式列印。縮小的程式碼通常等同於未縮小的程式碼,但較小,這表示下載速度較快,但較難偵錯。通常您會在生產環境中縮小程式碼,但在開發環境中不會。
在 esbuild 中啟用縮小如下所示
echo 'fn = obj => { return obj.x }' | esbuild --minify
fn=n=>n.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
minify: true,
})).code
'fn=n=>n.x;\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "fn = obj => { return obj.x }"
result := api.Transform(js, api.TransformOptions{
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
此選項會結合執行三項不同的工作:移除空白、將您的語法改寫為更精簡,以及將區域變數重新命名為較短的名稱。通常您會想要執行所有這些工作,但必要時也可以個別啟用這些選項
echo 'fn = obj => { return obj.x }' | esbuild --minify-whitespace
fn=obj=>{return obj.x};
echo 'fn = obj => { return obj.x }' | esbuild --minify-identifiers
fn = (n) => {
return n.x;
};
echo 'fn = obj => { return obj.x }' | esbuild --minify-syntax
fn = (obj) => obj.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
minifyWhitespace: true,
})).code
'fn=obj=>{return obj.x};\n'
(await esbuild.transform(js, {
minifyIdentifiers: true,
})).code
'fn = (n) => {\n return n.x;\n};\n'
(await esbuild.transform(js, {
minifySyntax: true,
})).code
'fn = (obj) => obj.x;\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
css := "div { color: yellow }"
result1 := api.Transform(css, api.TransformOptions{
Loader: api.LoaderCSS,
MinifyWhitespace: true,
})
if len(result1.Errors) == 0 {
fmt.Printf("%s", result1.Code)
}
result2 := api.Transform(css, api.TransformOptions{
Loader: api.LoaderCSS,
MinifyIdentifiers: true,
})
if len(result2.Errors) == 0 {
fmt.Printf("%s", result2.Code)
}
result3 := api.Transform(css, api.TransformOptions{
Loader: api.LoaderCSS,
MinifySyntax: true,
})
if len(result3.Errors) == 0 {
fmt.Printf("%s", result3.Code)
}
}
這些相同的概念也適用於 CSS,而不僅僅是 JavaScript
echo 'div { color: yellow }' | esbuild --loader=css --minify
div{color:#ff0}
import * as esbuild from 'esbuild'var css = 'div { color: yellow }'
(await esbuild.transform(css, {
loader: 'css',
minify: true,
})).code
'div{color:#ff0}\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
css := "div { color: yellow }"
result := api.Transform(css, api.TransformOptions{
Loader: api.LoaderCSS,
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
esbuild 中的 JavaScript 縮小演算法通常會產生非常接近業界標準 JavaScript 縮小工具的縮小輸出大小的輸出。 此基準測試有一個範例比較不同縮小工具之間的輸出大小。儘管 esbuild 並非在所有情況下都是最佳的 JavaScript 縮小工具(也並未嘗試成為最佳),但它力求為大多數程式碼產生縮小輸出,大小在專用縮小工具的幾百分比之內,當然也比其他工具快得多。
#注意事項
在使用 esbuild 作為縮小工具時,請記住以下事項
當啟用縮小功能時,您可能也應該設定 目標 選項。預設情況下,esbuild 會利用現代 JavaScript 功能來縮小您的程式碼。例如,
a ===
可以縮小為undefined || a === null ? 1 : a a ?? 1
。如果您不希望 esbuild 在縮小時利用現代 JavaScript 功能,則應使用較舊的語言目標,例如--target=es6
。字元跳脫序列
\n
會在 JavaScript 範本字串中替換為換行字元。如果 目標 支援字串字串,且這麼做會產生較小的輸出,則字串字串也會轉換為範本字串。這不是錯誤。縮小表示您要求較小的輸出,而跳脫序列\n
佔用兩個位元組,而換行字元只佔用一個位元組。預設情況下,esbuild 不會縮小頂層宣告的名稱。這是因為 esbuild 不知道您將如何處理輸出。您可能會將縮小的程式碼注入到其他一些程式碼的中央,在這種情況下,縮小頂層宣告名稱是不安全的。設定輸出 格式(或啟用 綑綁,如果您尚未設定輸出格式,它會為您選擇輸出格式)會告訴 esbuild 輸出將在其自己的範圍內執行,這表示縮小頂層宣告名稱是安全的。
縮小並非適用於所有 JavaScript 程式碼的 100%。這對 esbuild 以及其他受歡迎的 JavaScript 縮小程式,例如 terser,都是如此。特別是,esbuild 並未設計為保留對函式呼叫
.toString()
的值。這是因為如果所有函式中的所有程式碼都必須逐字保留,縮小幾乎完全無用,且幾乎沒有用處。不過,這表示仰賴.toString()
回傳值的 JavaScript 程式碼,在縮小時可能會中斷。例如,AngularJS 架構中的一些模式在縮小程式碼時會中斷,因為 AngularJS 使用.toString()
來讀取函式的引數名稱。解決方法是使用 明確註解。預設情況下,esbuild 不会保留函式和類別物件上的
.name
值。這是因為大多數程式碼並未依賴此屬性,而使用較短的名稱是重要的尺寸最佳化。不過,有些程式碼確實依賴.name
屬性來進行註冊和繫結。如果您需要依賴此屬性,您應該啟用 保留名稱 選項。使用某些 JavaScript 功能會停用 esbuild 的許多最佳化,包括縮小。特別是,使用直接
eval
和/或with
陳述式會阻止 esbuild 將識別碼重新命名為較小的名稱,因為這些功能會導致識別碼繫結在執行時間而非編譯時間發生。這幾乎總是無意的,而且只會發生在人們不知道直接eval
是什麼以及為何它很糟糕的情況下。如果您正在考慮撰寫類似這樣的程式碼
// Direct eval (will disable minification for the whole file) let result = eval(something)
您應該改寫成這樣,讓您的程式碼可以縮小
// Indirect eval (has no effect on the surrounding code) let result = (0, eval)(something)
這裡有更多關於直接
eval
的後果和可用替代方案的資訊 在此。esbuild 中的縮小演算法尚未進行進階程式碼最佳化。特別是,下列程式碼最佳化適用於 JavaScript 程式碼,但並未由 esbuild 執行(非詳盡清單)
- 函式主體內的死碼消除
- 函式內嵌
- 跨陳述式常數傳遞
- 物件形狀建模
- 配置下沉
- 方法去虛擬化
- 符號執行
- JSX 表達式提升
- TypeScript 列舉偵測和內聯
如果你的程式碼使用需要這些程式碼最佳化形式才能精簡的模式,或者如果你正在尋找最適合你的使用案例的 JavaScript 最小化演算法,你應該考慮使用其他工具。實作這些進階程式碼最佳化的工具範例包括 Terser 和 Google Closure Compiler。
#純粹
各種 JavaScript 工具使用慣例,其中包含 /* @__PURE__ */
或 /* #__PURE__ */
的特殊註解,表示在 new 或呼叫表達式之前,如果結果值未使用,則可以移除該表達式。它看起來像這樣
let button = /* @__PURE__ */ React.createElement(Button, null);
此資訊會由 esbuild 等打包器在樹狀搖晃(又稱移除無用程式碼)期間使用,以在打包器無法自行證明移除是安全的(由於 JavaScript 程式碼的動態特性)時,精細地移除模組邊界之間未使用的匯入。
請注意,雖然註解寫著「純粹」,但令人困惑的是,它並未表示正在呼叫的函式是純粹的。例如,它並未表示可以快取重複呼叫該函式。該名稱基本上只是「如果未使用,則可以移除」的抽象簡寫。
某些表達式,例如 JSX 和某些內建全域變數,會在 esbuild 中自動註解為 /* @__PURE__ */
。你也可以設定其他全域變數標記為 /* @__PURE__ */
。例如,你可以將全域變數 document.
函式標記為這樣,只要結果未使用,就可以在打包時自動從你的套件中移除。
值得一提的是,註解的效果僅延伸到呼叫本身,而不是引數。即使啟用了最小化,仍會保留具有副作用的引數
echo 'document.createElement(elemName())' | esbuild --pure:document.createElement
/* @__PURE__ */ document.createElement(elemName());
echo 'document.createElement(elemName())' | esbuild --pure:document.createElement --minify
elemName();
import * as esbuild from 'esbuild'let js = 'document.createElement(elemName())'
(await esbuild.transform(js, {
pure: ['document.createElement'],
})).code
'/* @__PURE__ */ document.createElement(elemName());\n'
(await esbuild.transform(js, {
pure: ['document.createElement'],
minify: true,
})).code
'elemName();\n'
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "document.createElement(elemName())"
result1 := api.Transform(js, api.TransformOptions{
Pure: []string{"document.createElement"},
})
if len(result1.Errors) == 0 {
fmt.Printf("%s", result1.Code)
}
result2 := api.Transform(js, api.TransformOptions{
Pure: []string{"document.createElement"},
MinifySyntax: true,
})
if len(result2.Errors) == 0 {
fmt.Printf("%s", result2.Code)
}
}
請注意,如果你嘗試移除對 console
API 方法(例如 console.log
)的所有呼叫,並且還想要移除對具有副作用的引數的評估,則有一個特別案例可用於此:你可以使用 drop 功能,而不是將 console
API 呼叫標記為純粹。但是,此機制特定於 console
API,不適用於其他呼叫表達式。
#樹狀搖晃
樹狀搖晃是 JavaScript 社群用於移除無用程式碼的術語,這是一種常見的編譯器最佳化,可以自動移除無法到達的程式碼。在 esbuild 中,此術語特別指宣告層級的無用程式碼移除。
樹狀搖晃最容易透過範例說明。考慮以下檔案。有一個已使用的函式和一個未使用的函式
// input.js
function one() {
console.log('one')
}
function two() {
console.log('two')
}
one()
如果你使用 esbuild
將此檔案打包,則未使用的函式將自動捨棄,留下以下輸出
// input.js
function one() {
console.log("one");
}
one();
即使我們將函數拆分到一個獨立的函式庫檔案中,並使用 import
語句匯入它們,這也能運作
// lib.js
export function one() {
console.log('one')
}
export function two() {
console.log('two')
}
// input.js
import * as lib from './lib.js'
lib.one()
如果您使用 esbuild
將此檔案打包,未使用的函數和未使用的匯入仍然會自動捨棄,讓您得到以下輸出
// lib.js
function one() {
console.log("one");
}
// input.js
one();
透過這種方式,esbuild 只會打包您實際使用的套件部分,有時這能大幅節省大小。請注意,esbuild 的 tree shaking 實作仰賴 ECMAScript 模組 import
和 export
語句。它不適用於 CommonJS 模組。npm 上的許多套件都包含這兩種格式,而 esbuild 預設會嘗試選擇適用於 tree shaking 的格式。您可以使用 main 欄位 和/或 條件 選項自訂 esbuild 選擇的格式,視套件而定。
預設情況下,tree shaking 僅在啟用 打包 時或將輸出 格式 設定為 iife
時才會啟用,否則 tree shaking 會停用。您可以將其設定為 true
以強制啟用 tree shaking
esbuild app.js --tree-shaking=true
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
treeShaking: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
TreeShaking: api.TreeShakingTrue,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
您也可以將其設定為 false
以強制停用 tree shaking
esbuild app.js --tree-shaking=false
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
treeShaking: false,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
TreeShaking: api.TreeShakingFalse,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#Tree shaking 和副作用
用於 tree shaking 的副作用偵測是保守的,這表示 esbuild 僅在確定沒有隱藏的副作用時,才會將程式碼視為可移除的無用程式碼。例如,12.34
和 "abcd"
等原始文字沒有副作用,可以移除,而 "ab" + cd
和 foo.bar
等表達式則有副作用(串接字串會呼叫 toString()
,這可能會產生副作用,而成員存取可能會呼叫 getter,這也可能會產生副作用)。即使參照全域識別碼也視為副作用,因為如果沒有同名的全域變數,它會擲出 ReferenceError
。以下是範例
// These are considered side-effect free
let a = 12.34;
let b = "abcd";
let c = { a: a };
// These are not considered side-effect free
// since they could cause some code to run
let x = "ab" + cd;
let y = foo.bar;
let z = { [x]: x };
有時,即使無法自動判斷某段程式碼沒有副作用,也希望允許程式碼被 tree shaken。這可以使用 pure 註解註解 來達成,它會告知 esbuild 信任程式碼作者,註解程式碼中沒有副作用。註解註解為 /* @__PURE__ */
,且只能置於 new 或 call 表達式之前。您可以註解立即呼叫函式表達式,並在函式本體中放置任意副作用
// This is considered side-effect free due to
// the annotation, and will be removed if unused
let gammaTable = /* @__PURE__ */ (() => {
// Side-effect detection is skipped in here
let table = new Uint8Array(256);
for (let i = 0; i < 256; i++)
table[i] = Math.pow(i / 255, 2.2) * 255;
return table;
})();
雖然 /* @__PURE__ */
僅適用於 call 表達式的事實有時會使程式碼更冗長,但此語法的最大好處是它可以在 JavaScript 生態系統中的許多其他工具中移植,包括熱門的 UglifyJS 和 Terser JavaScript 壓縮器(其他主要工具使用這些壓縮器,包括 Webpack 和 Parcel)。
請注意,註解會導致 esbuild 假設註解程式碼沒有副作用。如果註解錯誤,而程式碼實際上確實有重要的副作用,這些註解可能會導致程式碼損毀。如果您正在綑綁註解錯誤的第三方程式碼,您可能需要啟用 忽略註解,以確保綑綁程式碼正確無誤。
#原始碼地圖
#原始碼根目錄
此功能僅在啟用 原始碼地圖 時才相關。它讓您設定原始碼地圖中 sourceRoot
欄位的數值,該欄位指定原始碼地圖中所有其他路徑的相對路徑。如果此欄位不存在,原始碼地圖中的所有路徑都會被解釋為相對於包含原始碼地圖的目錄。
您可以這樣設定 sourceRoot
esbuild app.js --sourcemap --source-root=https://raw.githubusercontent.com/some/repo/v1.2.3/
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
sourcemap: true,
sourceRoot: 'https://raw.githubusercontent.com/some/repo/v1.2.3/',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Sourcemap: api.SourceMapInline,
SourceRoot: "https://raw.githubusercontent.com/some/repo/v1.2.3/",
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#原始碼檔案
此選項會在使用沒有檔名的輸入時設定檔名。這會在使用轉換 API 和在使用建置 API 與 stdin 時發生。設定的檔名會反映在錯誤訊息和原始碼地圖中。如果沒有設定,檔名預設為 <stdin>
。它可以這樣設定
cat app.js | esbuild --sourcefile=example.js --sourcemap
import * as esbuild from 'esbuild'
import fs from 'node:fs'
let js = fs.readFileSync('app.js', 'utf8')
let result = await esbuild.transform(js, {
sourcefile: 'example.js',
sourcemap: 'inline',
})
console.log(result.code)
package main
import "fmt"
import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js, err := ioutil.ReadFile("app.js")
if err != nil {
panic(err)
}
result := api.Transform(string(js),
api.TransformOptions{
Sourcefile: "example.js",
Sourcemap: api.SourceMapInline,
})
if len(result.Errors) == 0 {
fmt.Printf("%s %s", result.Code)
}
}
#原始碼地圖
原始碼地圖可以讓您更容易除錯您的程式碼。它們編碼從已產生輸出檔中的行/欄位偏移量轉換回對應原始輸入檔中的行/欄位偏移量所需的資訊。如果您的已產生程式碼與您的原始程式碼有顯著不同(例如,您的原始程式碼是 TypeScript 或您啟用了 縮小),這會很有用。如果您偏好於在瀏覽器的開發人員工具中查看個別檔案,而不是一個大的綑綁檔案,這也會很有用。
請注意,JavaScript 和 CSS 都支援原始碼對應輸出,且兩者套用相同的選項。以下所有關於 .js
檔案的說明,也同樣適用於 .css
檔案。
原始碼對應產生有四種不同的模式
-
連結
此模式表示原始碼對應會產生於
.js
輸出檔案旁的獨立.js.map
輸出檔案中,且.js
輸出檔案包含指向.js.map
輸出檔案的特殊//# sourceMappingURL=
註解。如此一來,當您開啟偵錯工具時,瀏覽器便會知道在哪裡找到特定檔案的原始碼對應。請使用下列方式使用linked
原始碼對應模式
esbuild app.ts --sourcemap --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
sourcemap: true,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Sourcemap: api.SourceMapLinked,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
-
外部
此模式表示原始碼對應會產生於
.js
輸出檔案旁的獨立.js.map
輸出檔案中,但與linked
模式不同的是,.js
輸出檔案不包含//# sourceMappingURL=
註解。請使用下列方式使用external
原始碼對應模式
esbuild app.ts --sourcemap=external --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
sourcemap: 'external',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Sourcemap: api.SourceMapExternal,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
-
內嵌
此模式表示原始碼對應會附加在
.js
輸出檔案的結尾,作為//# sourceMappingURL=
註解內的 base64 payload。不會產生其他.js.map
輸出檔案。請記住,原始碼對應通常非常大,因為它們包含所有原始原始碼,因此您通常不希望傳送包含inline
原始碼對應的程式碼。若要從原始碼對應中移除原始碼(僅保留檔案名稱和行/欄位對應),請使用 原始碼內容 選項。請使用下列方式使用inline
原始碼對應模式
esbuild app.ts --sourcemap=inline --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
sourcemap: 'inline',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Sourcemap: api.SourceMapInline,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
-
both
此模式結合了
inline
和external
。原始碼對應會以內嵌方式附加在.js
輸出檔案的結尾,且相同原始碼對應的另一個副本會寫入.js
輸出檔案旁的獨立.js.map
輸出檔案中。請使用下列方式使用both
原始碼對應模式
esbuild app.ts --sourcemap=both --outfile=out.js
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.ts'],
sourcemap: 'both',
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.ts"},
Sourcemap: api.SourceMapInlineAndExternal,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
上述 build API 支援所有四種原始碼對應模式,但 transform API 不支援 linked
模式。這是因為從 transform API 回傳的輸出沒有關聯的檔案名稱。如果您希望 transform API 的輸出具有原始碼對應註解,您可以自行附加一個。此外,transform API 的 CLI 形式僅支援 inline
模式,因為輸出會寫入 stdout,因此無法產生多個輸出檔案。
如果您想「一窺究竟」原始程式碼地圖的作用(或偵錯原始程式碼地圖的問題),您可以上傳相關的輸出檔案和關聯的原始程式碼地圖:原始程式碼地圖視覺化。
#使用原始程式碼地圖
在瀏覽器中,只要啟用了原始程式碼地圖設定,瀏覽器的開發人員工具就會自動擷取原始程式碼地圖。請注意,瀏覽器只會在堆疊追蹤記錄到主控台時,使用原始程式碼地圖來變更堆疊追蹤的顯示。堆疊追蹤本身並未修改,因此在您的程式碼中檢查 error.
仍會提供包含已編譯程式碼的未對應堆疊追蹤。以下是如何在瀏覽器的開發人員工具中啟用此設定
- Chrome:⚙ → 啟用 JavaScript 原始程式碼地圖
- Safari:⚙ → 來源 → 啟用原始程式碼地圖
- Firefox:··· → 啟用原始程式碼地圖
在 node 中,原始程式碼地圖原生支援從 版本 v12.12.0 開始。此功能預設停用,但可以使用旗標啟用。與瀏覽器不同,實際堆疊追蹤也會在 node 中修改,因此在您的程式碼中檢查 error.
會提供包含原始原始程式碼的對應堆疊追蹤。以下是如何在 node 中啟用此設定(--enable-
旗標必須在指令碼檔案名稱之前)
node --enable-source-maps app.js
#來源內容
原始程式碼地圖 使用 版本 3 的原始程式碼地圖格式產生,這是迄今為止支援最廣泛的變體。每個原始程式碼地圖看起來都像這樣
{
"version": 3,
"sources": ["bar.js", "foo.js"],
"sourcesContent": ["bar()", "foo()\nimport './bar'"],
"mappings": ";AAAA;;;ACAA;",
"names": []
}
sourcesContent
欄位是一個選用欄位,其中包含所有原始原始程式碼。這有助於偵錯,因為這表示原始原始程式碼將在偵錯器中可用。
但是,在某些情況下並不需要。例如,如果您只是在製作環境中使用原始程式碼地圖來產生包含原始檔案名稱的堆疊追蹤,則不需要原始原始程式碼,因為沒有涉及偵錯器。在這種情況下,可以省略 sourcesContent
欄位以縮小原始程式碼地圖
esbuild --bundle app.js --sourcemap --sources-content=false
import * as esbuild from 'esbuild'
await esbuild.build({
bundle: true,
entryPoints: ['app.js'],
sourcemap: true,
sourcesContent: false,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
Bundle: true,
EntryPoints: []string{"app.js"},
Sourcemap: api.SourceMapInline,
SourcesContent: api.SourcesContentExclude,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#建置元資料
#分析
支援:建立
使用分析功能會產生一份關於您的套件內容的易於閱讀的報告
esbuild --bundle example.jsx --outfile=out.js --minify --analyze out.js 27.6kb 100.0% ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 19.2kb 69.8% ├ node_modules/react/cjs/react.production.min.js 5.9kb 21.4% ├ node_modules/object-assign/index.js 962b 3.4% ├ example.jsx 137b 0.5% ├ node_modules/react-dom/server.browser.js 50b 0.2% └ node_modules/react/index.js 50b 0.2% ...
import * as esbuild from 'esbuild'
let result = await esbuild.build({
entryPoints: ['example.jsx'],
outfile: 'out.js',
minify: true,
metafile: true,
})
console.log(await esbuild.analyzeMetafile(result.metafile))
package main
import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"example.jsx"},
Outfile: "out.js",
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
Metafile: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{}))
}
此資訊顯示每個輸出檔案中包含的輸入檔案,以及這些檔案在輸出檔案中所佔的百分比。如果您需要其他資訊,可以啟用「詳細」模式。此模式目前會顯示從進入點到每個輸入檔案的匯入路徑,告訴您為何會將特定輸入檔案包含在套件中
esbuild --bundle example.jsx --outfile=out.js --minify --analyze=verbose out.js ─────────────────────────────────────────────────────────────────── 27.6kb ─ 100.0% ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js ─ 19.2kb ── 69.8% │ └ node_modules/react-dom/server.browser.js │ └ example.jsx ├ node_modules/react/cjs/react.production.min.js ───────────────────────── 5.9kb ── 21.4% │ └ node_modules/react/index.js │ └ example.jsx ├ node_modules/object-assign/index.js ──────────────────────────────────── 962b ──── 3.4% │ └ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js │ └ node_modules/react-dom/server.browser.js │ └ example.jsx ├ example.jsx ──────────────────────────────────────────────────────────── 137b ──── 0.5% ├ node_modules/react-dom/server.browser.js ──────────────────────────────── 50b ──── 0.2% │ └ example.jsx └ node_modules/react/index.js ───────────────────────────────────────────── 50b ──── 0.2% └ example.jsx ...
import * as esbuild from 'esbuild'
let result = await esbuild.build({
entryPoints: ['example.jsx'],
outfile: 'out.js',
minify: true,
metafile: true,
})
console.log(await esbuild.analyzeMetafile(result.metafile, {
verbose: true,
}))
package main
import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"example.jsx"},
Outfile: "out.js",
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
Metafile: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{
Verbose: true,
}))
}
此分析只是 元檔案 中資訊的可視化呈現。如果此分析無法完全滿足您的需求,歡迎您使用元檔案中的資訊建立自己的可視化呈現。
請注意,此格式化的分析摘要是針對人類而非機器所設計。特定格式可能會隨著時間而變更,這可能會損壞嘗試解析它的任何工具。您不應撰寫工具來解析此資料。您應改用 JSON 元資料檔案 中的資訊。此可視化呈現中的所有內容都來自 JSON 元資料,因此您不會因為不解析 esbuild 的格式化分析摘要而遺失任何資訊。
#元檔案
支援:建立
此選項會指示 esbuild 以 JSON 格式產生一些關於建置的元資料。以下範例會將元資料放入名為 meta.json
的檔案中
esbuild app.js --bundle --metafile=meta.json --outfile=out.js
import * as esbuild from 'esbuild'
import fs from 'node:fs'
let result = await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
metafile: true,
outfile: 'out.js',
})
fs.writeFileSync('meta.json', JSON.stringify(result.metafile))
package main
import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
Bundle: true,
Metafile: true,
Outfile: "out.js",
Write: true,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
ioutil.WriteFile("meta.json", []byte(result.Metafile), 0644)
}
此資料接著可以由其他工具進行分析。對於互動式可視化呈現,您可以使用 esbuild 自己的 套件大小分析器。對於快速文字分析,您可以使用 esbuild 內建的 分析 功能。或者,您可以撰寫自己的分析,使用此資訊。
元資料 JSON 格式如下(使用 TypeScript 介面描述)
interface Metafile {
inputs: {
[path: string]: {
bytes: number
imports: {
path: string
kind: string
external?: boolean
original?: string
with?: Record<string, string>
}[]
format?: string
with?: Record<string, string>
}
}
outputs: {
[path: string]: {
bytes: number
inputs: {
[path: string]: {
bytesInOutput: number
}
}
imports: {
path: string
kind: string
external?: boolean
}[]
exports: string[]
entryPoint?: string
cssBundle?: string
}
}
}
#記錄
#色彩
此選項會啟用或停用 esbuild 寫入終端機中的 stderr 檔案描述符的錯誤和警告訊息中的色彩。預設情況下,如果 stderr 是 TTY 會話,色彩會自動啟用,否則會自動停用。esbuild 中的彩色輸出如下所示
▲ [WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof] example.js:2:16: 2 │ log(typeof x == "null") ╵ ~~~~~~ The expression "typeof x" actually evaluates to "object" in JavaScript, not "null". You need to use "x === null" to test for null. ✘ [ERROR] Could not resolve "logger" example.js:1:16: 1 │ import log from "logger" ╵ ~~~~~~~~ You can mark the path "logger" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.
可以透過將色彩設定為 true
來強制啟用彩色輸出。如果您要將 esbuild 的 stderr 輸出導向 TTY,這會很有用
echo 'typeof x == "null"' | esbuild --color=true 2> stderr.txt
import * as esbuild from 'esbuild'
let js = 'typeof x == "null"'
await esbuild.transform(js, {
color: true,
})
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "typeof x == 'null'"
result := api.Transform(js, api.TransformOptions{
Color: api.ColorAlways,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
也可以將彩色輸出設定為 false
來停用色彩。
#格式化訊息
此 API 呼叫可用於格式化 建置 API 和 轉換 API 傳回的記錄錯誤和警告,並使用 esbuild 本身使用的相同格式化作為字串。如果您想要自訂 esbuild 記錄運作的方式,例如在列印記錄訊息之前處理這些訊息或將它們列印到主控台以外的位置,這會很有用。以下是一個範例
import * as esbuild from 'esbuild'
let formatted = await esbuild.formatMessages([
{
text: 'This is an error',
location: {
file: 'app.js',
line: 10,
column: 4,
length: 3,
lineText: 'let foo = bar',
},
},
], {
kind: 'error',
color: false,
terminalWidth: 100,
})
console.log(formatted.join('\n'))
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "strings"
func main() {
formatted := api.FormatMessages([]api.Message{
{
Text: "This is an error",
Location: &api.Location{
File: "app.js",
Line: 10,
Column: 4,
Length: 3,
LineText: "let foo = bar",
},
},
}, api.FormatMessagesOptions{
Kind: api.ErrorMessage,
Color: false,
TerminalWidth: 100,
})
fmt.Printf("%s", strings.Join(formatted, "\n"))
}
#選項
可以提供以下選項來控制格式化
interface FormatMessagesOptions {
kind: 'error' | 'warning';
color?: boolean;
terminalWidth?: number;
}
type FormatMessagesOptions struct {
Kind MessageKind
Color bool
TerminalWidth int
}
kind
控制這些日誌訊息以錯誤或警告列印。
color
如果這是
true
,Unix 風格的終端跳脫碼會包含在彩色輸出中。terminalWidth
提供正值來換行長行,這樣它們就不會超出提供的欄位寬度。提供
0
來停用換行。
#日誌層級
可以變更日誌層級以防止 esbuild 將警告和/或錯誤訊息列印到終端。六個日誌層級是
silent
不顯示任何日誌輸出。這是使用 JS transform API 時的預設日誌層級。error
只顯示錯誤。warning
只顯示警告和錯誤。這是使用 JS build API 時的預設日誌層級。info
顯示警告、錯誤和輸出檔案摘要。這是使用 CLI 時的預設日誌層級。debug
從info
記錄所有內容,以及一些額外的訊息,這些訊息可能有助於你除錯損毀的套件。此日誌層級會影響效能,而且有些訊息可能是誤報,因此預設情況下不會顯示這些資訊。verbose
這會產生大量日誌訊息,並已新增來除錯檔案系統驅動程式的問題。它不適合一般用途。
可以這樣設定日誌層級
echo 'typeof x == "null"' | esbuild --log-level=error
import * as esbuild from 'esbuild'
let js = 'typeof x == "null"'
await esbuild.transform(js, {
logLevel: 'error',
})
package main
import "fmt"
import "github.com/evanw/esbuild/pkg/api"
func main() {
js := "typeof x == 'null'"
result := api.Transform(js, api.TransformOptions{
LogLevel: api.LogLevelError,
})
if len(result.Errors) == 0 {
fmt.Printf("%s", result.Code)
}
}
#日誌限制
預設情況下,esbuild 在報告 10 則訊息後停止報告日誌訊息。這可以避免意外產生大量日誌訊息,這很容易鎖定較慢的終端機模擬器,例如 Windows 命令提示字元。它還可以避免意外用完具有有限捲動緩衝區的終端機模擬器的整個捲動緩衝區。
可以將日誌限制變更為其他值,也可以透過將其設定為零來完全停用。這將顯示所有日誌訊息
esbuild app.js --log-limit=0
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
logLimit: 0,
outfile: 'out.js',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
LogLimit: 0,
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
#日誌覆寫
此功能可讓你變更個別類型日誌訊息的日誌層級。你可以使用它來取消特定類型的警告,啟用預設情況下未啟用的其他警告,甚至將警告轉換為錯誤。
例如,在鎖定舊瀏覽器時,esbuild 會自動將使用對這些瀏覽器來說太新的功能的正規表示式文字轉換為 new
呼叫,以允許產生的程式碼執行,而不會被瀏覽器視為語法錯誤。但是,如果你沒有為 RegExp
新增多載,這些呼叫在執行時仍會擲回,因為正規表示式語法仍然不受支援。如果你希望 esbuild 在你使用較新的不受支援正規表示式語法時產生警告,你可以這樣做
esbuild app.js --log-override:unsupported-regexp=warning --target=chrome50
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['app.js'],
logOverride: {
'unsupported-regexp': 'warning',
},
target: 'chrome50',
})
package main
import "github.com/evanw/esbuild/pkg/api"
import "os"
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"app.js"},
LogOverride: map[string]api.LogLevel{
"unsupported-regexp": api.LogLevelWarning,
},
Engines: []api.Engine{
{Name: api.EngineChrome, Version: "50"},
},
})
if len(result.Errors) > 0 {
os.Exit(1)
}
}
每個訊息類型的記錄層級都可以覆寫成 記錄層級 設定支援的任何值。目前所有可用的訊息類型都列在下方(按一下每個類型以取得範例記錄訊息)
-
JS
assign-to-constant
▲ [WARNING] This assignment will throw because "foo" is a constant [assign-to-constant] example.js:1:15: 1 │ const foo = 1; foo = 2 ╵ ~~~ The symbol "foo" was declared a constant here: example.js:1:6: 1 │ const foo = 1; foo = 2 ╵ ~~~
assign-to-import
▲ [WARNING] This assignment will throw because "foo" is an import [assign-to-import] example.js:1:23: 1 │ import foo from "foo"; foo = null ╵ ~~~ Imports are immutable in JavaScript. To modify the value of this import, you must export a setter function in the imported file (e.g. "setFoo") and then import and call that function here instead.
call-import-namespace
▲ [WARNING] Calling "foo" will crash at run-time because it's an import namespace object, not a function [call-import-namespace] example.js:1:28: 1 │ import * as foo from "foo"; foo() ╵ ~~~ Consider changing "foo" to a default import instead: example.js:1:7: 1 │ import * as foo from "foo"; foo() │ ~~~~~~~~ ╵ foo
commonjs-variable-in-esm
▲ [WARNING] The CommonJS "exports" variable is treated as a global variable in an ECMAScript module and may not work as expected [commonjs-variable-in-esm] example.js:1:0: 1 │ exports.foo = 1; export let bar = 2 ╵ ~~~~~~~ This file is considered to be an ECMAScript module because of the "export" keyword here: example.js:1:17: 1 │ exports.foo = 1; export let bar = 2 ╵ ~~~~~~
delete-super-property
▲ [WARNING] Attempting to delete a property of "super" will throw a ReferenceError [delete-super-property] example.js:1:42: 1 │ class Foo extends Object { foo() { delete super.foo } } ╵ ~~~~~
duplicate-case
▲ [WARNING] This case clause will never be evaluated because it duplicates an earlier case clause [duplicate-case] example.js:1:33: 1 │ switch (foo) { case 1: return 1; case 1: return 2 } ╵ ~~~~ The earlier case clause is here: example.js:1:15: 1 │ switch (foo) { case 1: return 1; case 1: return 2 } ╵ ~~~~
duplicate-object-key
▲ [WARNING] Duplicate key "bar" in object literal [duplicate-object-key] example.js:1:16: 1 │ foo = { bar: 1, bar: 2 } ╵ ~~~ The original key "bar" is here: example.js:1:8: 1 │ foo = { bar: 1, bar: 2 } ╵ ~~~
empty-import-meta
▲ [WARNING] "import.meta" is not available in the configured target environment ("chrome50") and will be empty [empty-import-meta] example.js:1:6: 1 │ foo = import.meta ╵ ~~~~~~~~~~~
equals-nan
▲ [WARNING] Comparison with NaN using the "!==" operator here is always true [equals-nan] example.js:1:24: 1 │ foo = foo.filter(x => x !== NaN) ╵ ~~~ Floating-point equality is defined such that NaN is never equal to anything, so "x === NaN" always returns false. You need to use "Number.isNaN(x)" instead to test for NaN.
equals-negative-zero
▲ [WARNING] Comparison with -0 using the "!==" operator will also match 0 [equals-negative-zero] example.js:1:28: 1 │ foo = foo.filter(x => x !== -0) ╵ ~~ Floating-point equality is defined such that 0 and -0 are equal, so "x === -0" returns true for both 0 and -0. You need to use "Object.is(x, -0)" instead to test for -0.
equals-new-object
▲ [WARNING] Comparison using the "!==" operator here is always true [equals-new-object] example.js:1:24: 1 │ foo = foo.filter(x => x !== []) ╵ ~~~ Equality with a new object is always false in JavaScript because the equality operator tests object identity. You need to write code to compare the contents of the object instead. For example, use "Array.isArray(x) && x.length === 0" instead of "x === []" to test for an empty array.
html-comment-in-js
▲ [WARNING] Treating "<!--" as the start of a legacy HTML single-line comment [html-comment-in-js] example.js:1:0: 1 │ <!-- comment --> ╵ ~~~~
impossible-typeof
▲ [WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof] example.js:1:32: 1 │ foo = foo.map(x => typeof x !== "null") ╵ ~~~~~~ The expression "typeof x" actually evaluates to "object" in JavaScript, not "null". You need to use "x === null" to test for null.
indirect-require
▲ [WARNING] Indirect calls to "require" will not be bundled [indirect-require] example.js:1:8: 1 │ let r = require, fs = r("fs") ╵ ~~~~~~~
private-name-will-throw
▲ [WARNING] Writing to getter-only property "#foo" will throw [private-name-will-throw] example.js:1:39: 1 │ class Foo { get #foo() {} bar() { this.#foo++ } } ╵ ~~~~
semicolon-after-return
▲ [WARNING] The following expression is not returned because of an automatically-inserted semicolon [semicolon-after-return] example.js:1:6: 1 │ return ╵ ^
suspicious-boolean-not
▲ [WARNING] Suspicious use of the "!" operator inside the "in" operator [suspicious-boolean-not] example.js:1:4: 1 │ if (!foo in bar) { │ ~~~~ ╵ (!foo) The code "!x in y" is parsed as "(!x) in y". You need to insert parentheses to get "!(x in y)" instead.
this-is-undefined-in-esm
▲ [WARNING] Top-level "this" will be replaced with undefined since this file is an ECMAScript module [this-is-undefined-in-esm] example.js:1:0: 1 │ this.foo = 1; export let bar = 2 │ ~~~~ ╵ undefined This file is considered to be an ECMAScript module because of the "export" keyword here: example.js:1:14: 1 │ this.foo = 1; export let bar = 2 ╵ ~~~~~~
unsupported-dynamic-import
▲ [WARNING] This "import" expression will not be bundled because the argument is not a string literal [unsupported-dynamic-import] example.js:1:0: 1 │ import(foo) ╵ ~~~~~~
unsupported-jsx-comment
▲ [WARNING] Invalid JSX factory: 123 [unsupported-jsx-comment] example.jsx:1:8: 1 │ // @jsx 123 ╵ ~~~
unsupported-regexp
▲ [WARNING] The regular expression flag "d" is not available in the configured target environment ("chrome50") [unsupported-regexp] example.js:1:3: 1 │ /./d ╵ ^ This regular expression literal has been converted to a "new RegExp()" constructor to avoid generating code with a syntax error. However, you will need to include a polyfill for "RegExp" for your code to have the correct behavior at run-time.
unsupported-require-call
▲ [WARNING] This call to "require" will not be bundled because the argument is not a string literal [unsupported-require-call] example.js:1:0: 1 │ require(foo) ╵ ~~~~~~~
-
CSS
css-syntax-error
▲ [WARNING] Expected identifier but found "]" [css-syntax-error] example.css:1:4: 1 │ div[] { ╵ ^
invalid-@charset
▲ [WARNING] "@charset" must be the first rule in the file [invalid-@charset] example.css:1:19: 1 │ div { color: red } @charset "UTF-8"; ╵ ~~~~~~~~ This rule cannot come before a "@charset" rule example.css:1:0: 1 │ div { color: red } @charset "UTF-8"; ╵ ^
invalid-@import
▲ [WARNING] All "@import" rules must come first [invalid-@import] example.css:1:19: 1 │ div { color: red } @import "foo.css"; ╵ ~~~~~~~ This rule cannot come before an "@import" rule example.css:1:0: 1 │ div { color: red } @import "foo.css"; ╵ ^
invalid-@layer
▲ [WARNING] "initial" cannot be used as a layer name [invalid-@layer] example.css:1:7: 1 │ @layer initial { ╵ ~~~~~~~
invalid-calc
▲ [WARNING] "-" can only be used as an infix operator, not a prefix operator [invalid-calc] example.css:1:20: 1 │ div { z-index: calc(-(1+2)); } ╵ ^ ▲ [WARNING] The "+" operator only works if there is whitespace on both sides [invalid-calc] example.css:1:23: 1 │ div { z-index: calc(-(1+2)); } ╵ ^
js-comment-in-css
▲ [WARNING] Comments in CSS use "/* ... */" instead of "//" [js-comment-in-css] example.css:1:0: 1 │ // comment ╵ ~~
undefined-composes-from
▲ [WARNING] The value of "zoom" in the "foo" class is undefined [undefined-composes-from] example.module.css:1:1: 1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; } ╵ ~~~ The first definition of "zoom" is here: lib.module.css:1:7: 1 │ .bar { zoom: 2 } ╵ ~~~~ The second definition of "zoom" is here: example.module.css:1:44: 1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; } ╵ ~~~~ The specification of "composes" does not define an order when class declarations from separate files are composed together. The value of the "zoom" property for "foo" may change unpredictably as the code is edited. Make sure that all definitions of "zoom" for "foo" are in a single file.
unsupported-@charset
▲ [WARNING] "UTF-8" will be used instead of unsupported charset "ASCII" [unsupported-@charset] example.css:1:9: 1 │ @charset "ASCII"; ╵ ~~~~~~~
unsupported-@namespace
▲ [WARNING] "@namespace" rules are not supported [unsupported-@namespace] example.css:1:0: 1 │ @namespace "ns"; ╵ ~~~~~~~~~~
unsupported-css-property
▲ [WARNING] "widht" is not a known CSS property [unsupported-css-property] example.css:1:6: 1 │ div { widht: 1px } │ ~~~~~ ╵ width Did you mean "width" instead?
unsupported-css-nesting
▲ [WARNING] Transforming this CSS nesting syntax is not supported in the configured target environment ("chrome50") [unsupported-css-nesting] example.css:2:5: 2 │ .foo & { ╵ ^ The nesting transform for this case must generate an ":is(...)" but the configured target environment does not support the ":is" pseudo-class.
-
Bundler
ambiguous-reexport
▲ [WARNING] Re-export of "foo" in "example.js" is ambiguous and has been removed [ambiguous-reexport] One definition of "foo" comes from "a.js" here: a.js:1:11: 1 │ export let foo = 1 ╵ ~~~ Another definition of "foo" comes from "b.js" here: b.js:1:11: 1 │ export let foo = 2 ╵ ~~~
different-path-case
▲ [WARNING] Use "foo.js" instead of "Foo.js" to avoid issues with case-sensitive file systems [different-path-case] example.js:2:7: 2 │ import "./Foo.js" ╵ ~~~~~~~~~~
empty-glob
▲ [WARNING] The glob pattern import("./icon-*.json") did not match any files [empty-glob] example.js:2:16: 2 │ return import("./icon-" + name + ".json") ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~
ignored-bare-import
▲ [WARNING] Ignoring this import because "node_modules/foo/index.js" was marked as having no side effects [ignored-bare-import] example.js:1:7: 1 │ import "foo" ╵ ~~~~~ "sideEffects" is false in the enclosing "package.json" file: node_modules/foo/package.json:2:2: 2 │ "sideEffects": false ╵ ~~~~~~~~~~~~~
ignored-dynamic-import
▲ [WARNING] Importing "foo" was allowed even though it could not be resolved because dynamic import failures appear to be handled here: [ignored-dynamic-import] example.js:1:7: 1 │ import("foo").catch(e => { ╵ ~~~~~ The handler for dynamic import failures is here: example.js:1:14: 1 │ import("foo").catch(e => { ╵ ~~~~~
import-is-undefined
▲ [WARNING] Import "foo" will always be undefined because the file "foo.js" has no exports [import-is-undefined] example.js:1:9: 1 │ import { foo } from "./foo" ╵ ~~~
require-resolve-not-external
▲ [WARNING] "foo" should be marked as external for use with "require.resolve" [require-resolve-not-external] example.js:1:26: 1 │ let foo = require.resolve("foo") ╵ ~~~~~
-
原始碼對應
invalid-source-mappings
▲ [WARNING] Bad "mappings" data in source map at character 3: Invalid original column value: -2 [invalid-source-mappings] example.js.map:2:18: 2 │ "mappings": "aAAFA,UAAU;;" ╵ ^ The source map "example.js.map" was referenced by the file "example.js" here: example.js:1:21: 1 │ //# sourceMappingURL=example.js.map ╵ ~~~~~~~~~~~~~~
sections-in-source-map
▲ [WARNING] Source maps with "sections" are not supported [sections-in-source-map] example.js.map:2:2: 2 │ "sections": [] ╵ ~~~~~~~~~~ The source map "example.js.map" was referenced by the file "example.js" here: example.js:1:21: 1 │ //# sourceMappingURL=example.js.map ╵ ~~~~~~~~~~~~~~
missing-source-map
▲ [WARNING] Cannot read file ".": is a directory [missing-source-map] example.js:1:21: 1 │ //# sourceMappingURL=. ╵ ^
unsupported-source-map-comment
▲ [WARNING] Unsupported source map comment: could not decode percent-escaped data: invalid URL escape "%\"" [unsupported-source-map-comment] example.js:1:21: 1 │ //# sourceMappingURL=data:application/json,"%" ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~
-
Resolver
package.json
▲ [WARNING] "esm" is not a valid value for the "type" field [package.json] package.json:1:10: 1 │ { "type": "esm" } ╵ ~~~~~ The "type" field must be set to either "commonjs" or "module".
tsconfig.json
▲ [WARNING] Unrecognized target environment "ES4" [tsconfig.json] tsconfig.json:1:33: 1 │ { "compilerOptions": { "target": "ES4" } } ╵ ~~~~~
這些訊息類型應相當穩定,但未來可能會新增新的訊息類型,偶爾也會移除舊的訊息類型。如果移除某個訊息類型,該訊息類型的任何覆寫都會被靜默略過。