執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

獨立執行檔

使用 Bun 從 TypeScript 或 JavaScript 檔案生成獨立執行檔

Bun 的打包器支援 --compile 標誌,用於從 TypeScript 或 JavaScript 檔案生成獨立的二進位制執行檔。

terminal
bun build ./cli.ts --compile --outfile mycli
cli.ts
console.log("Hello world!");

Bun 會將 cli.ts 打包為可直接執行的執行檔:

terminal
./mycli
Text
Hello world!

Bun 會將所有匯入的檔案和包以及一份 Bun 執行環境副本打包到執行檔中。所有內建的 Bun 和 Node.js API 均受支援。


跨平臺交叉編譯

使用 --target 標誌,可以將獨立執行檔編譯為與執行 bun build 的機器不同的作業系統、架構或 Bun 版本。

建置 Linux x64 版本(大多數伺服器):

terminal
bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

建置 Linux ARM64 版本(例如 Graviton 或 Raspberry Pi):

terminal
# 注意:如果未指定架構,預設架構為 x64。
bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp

建置 Windows x64 版本:

terminal
bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp

# note: if no .exe extension is provided, Bun adds it automatically for Windows executables

為 Windows arm64 建置:

terminal
bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp

# 注意:如果未提供 .exe 副檔名,Bun 會自動為 Windows 執行檔新增該副檔名

建置 macOS arm64 版本:

terminal
bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp

建置 macOS x64 版本:

terminal
bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp

支援的目標平臺

--target 值的各個部分可以按任意順序出現,只要它們使用 - 分隔即可。

--target作業系統架構Libc
bun-linux-x64Linuxx64glibc
bun-linux-arm64Linuxarm64glibc
bun-windows-x64Windowsx64-
bun-windows-arm64Windowsarm64-
bun-darwin-x64macOSx64-
bun-darwin-arm64macOSarm64-
bun-linux-x64-muslLinuxx64musl
bun-linux-arm64-muslLinuxarm64musl

編譯時常量

使用 --define 標誌可將編譯時常量注入執行檔,例如版本號、建置時間戳或設定值:

terminal
bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli

Bun 會在建置時將這些常量內聯到二進位制檔案中,因此它們不會產生任何執行環境開銷,並且能夠實現死程式碼消除。


生產環境部署

編譯後的執行檔降低記憶體使用並提升 Bun 啟動速度。

通常情況下,Bun 會在 import 和 require 時讀取並轉譯 JavaScript 和 TypeScript 檔案。這正是 Bun 能夠“開箱即用”的原因之一,但這並非沒有代價:從磁碟讀取檔案、解析路徑、解析程式碼、轉譯以及列印原始碼都會消耗時間和記憶體。

編譯後的執行檔將這些開銷從執行環境轉移到了建置時。

部署到生產環境推薦做法:

terminal
bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp

位元組碼編譯

為提升啟動速度,啟用位元組碼編譯:

terminal
bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp

使用位元組碼編譯,tsc 啟動速度提升兩倍:

位元組碼效能比較

位元組碼編譯會將大型輸入檔案的解析開銷從執行環境轉移到打包時。你的應用啟動更快,但 bun build 命令會稍微變慢。位元組碼編譯不會隱藏原始碼。

各個標誌的作用

--minify 引數會減小轉譯後輸出程式碼的大小。對於大型應用,這可以節省數兆位元組的空間。對於較小的應用,它仍可能略微改善啟動時間。

--sourcemap 引數會嵌入使用 zstd 壓縮的源對映,這樣錯誤和堆疊跟蹤就會指向其原始位置,而不是轉譯後的位置。發生錯誤時,Bun 會自動解壓並解析源對映。

--bytecode 引數會啟用位元組碼編譯。每次在 Bun 中執行 JavaScript 程式碼時,JavaScriptCore(引擎)都會將原始碼編譯為位元組碼。--bytecode 會將這部分解析工作從執行環境轉移到打包時,從而縮短啟動時間。

使用 --bytecode 時,JavaScriptCore 的建置時最佳化過程也會對生成的位元組碼執行;--no-optimize-bytecode(Bun.build 中的 optimize: { bytecode: false })則會跳過這些過程。

基於設定檔引導的位元組碼佈局

--bytecode-order 是針對 --bytecode 的設定檔引導最佳化(PGO)。執行一次執行檔以記錄它使用的函式,然後使用該設定檔再次建置。Bun 會將執行期間使用的位元組碼集中放在檔案開頭,這樣執行檔讀取和解碼自身的內容就會更少:啟動更快,記憶體佔用也更低。

在一個大型 CLI 應用中,冷啟動時間從 1.0 秒降至 0.53 秒,啟動時的記憶體佔用減少了 40 MB。

shell
# 1. Build
bun build --compile --bytecode ./cli.ts --outfile myapp

# 2. Run it the way your users do. The profile is written on exit.
BUN_BYTECODE_ORDER_OUT=myapp.order ./myapp --help

# 3. Build again with the profile
bun build --compile --bytecode --bytecode-order=myapp.order ./cli.ts --outfile myapp

在 CI 中,記錄設定檔並在同一任務中使用它:

.github/workflows/release.yml
name: Release
on:
  push:
    tags: ["v*"]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install

      - name: Build
        run: bun build --compile --bytecode ./cli.ts --outfile myapp

      - name: Record a profile
        run: ./myapp --help
        env:
          BUN_BYTECODE_ORDER_OUT: myapp.order

      - name: Build with the profile
        run: bun build --compile --bytecode --bytecode-order=myapp.order ./cli.ts --outfile myapp

      - uses: actions/upload-artifact@v4
        with:
          name: myapp
          path: myapp

使用 Bun.build:

build.ts
await Bun.build({
  entrypoints: ["./cli.ts"],
  bytecode: true,
  compile: { outfile: "./myapp", bytecodeOrder: "./myapp.order" },
});
  • 程式碼變更後,設定檔仍然有效。 函式根據語法雜湊進行匹配,而非名稱或位置,因此上週建置生成的設定檔仍然適用。如果每次建置時都記錄設定檔不方便,可以將其提交到版本控制或快取。自記錄後發生變化的函式會放在已記錄函式之後。
  • 一個設定檔適用於所有 --target。
  • 多個設定檔可覆蓋多種應用啟動方式:--bytecode-order=interactive.order,oneshot.order。將最常見的方式列在前面。
  • 只有佈局會發生變化。 程式行為保持不變,建置仍然可復現。設定檔包含的是雜湊,而非原始碼。
  • BUN_BYTECODE_ORDER_OUT 中的 %p 會替換為行程 ID。Worker 執行緒也會被記錄。
  • bun:jsc 中的 bytecodeOrderStats() 會報告設定檔覆蓋(hot)或未覆蓋(cold)的已載入函式數量。如果 cold 的佔比不斷上升,就該重新記錄了。

JIT 策略

只在啟動期間執行的程式碼通常無法抵消 JIT 編譯它所花費的時間。--compile-jit-policy <n>(Bun.build 中的 compile: { jitPolicy: n })會將 JavaScriptCore 的層級提升閾值乘以 n,從而讓啟動程式碼在直譯器中停留更長時間。預設值 1 表示常規 JIT 策略。該倍數會一直生效,直到程式呼叫 Bun.unsafe.setJITPolicy(1);程式應在進入互動狀態後呼叫此函式:

build.ts
await Bun.build({
  entrypoints: ["./app.ts"],
  compile: { outfile: "./myapp", jitPolicy: 8 },
  bytecode: true,
});
app.ts
await renderFirstScreen();
Bun.unsafe.setJITPolicy(1); // back to the normal JIT policy

嵌入執行環境引數

--compile-exec-argv="args" - 嵌入執行環境引數,可在執行環境通過 process.execArgv 獲取:

terminal
bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp
app.ts
// 在編譯後的程式中
console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"]

通過 BUN_OPTIONS 傳入執行環境引數

獨立執行檔會讀取 BUN_OPTIONS 環境變數,因此你可以無需重新編譯即可傳入執行環境標誌:

terminal
# 在編譯後的執行檔上啟用 CPU 效能分析
BUN_OPTIONS="--cpu-prof" ./myapp

# 啟用堆效能分析並輸出 Markdown 格式
BUN_OPTIONS="--heap-prof-md" ./myapp

# 組合多個標誌
BUN_OPTIONS="--smol --cpu-prof-md" ./myapp

自動載入設定

獨立執行檔可以自動從執行目錄載入設定檔。預設:

  • 停用 載入 tsconfig.json 和 package.json — 這些通常只在開發時需要,編譯時已經用過了
  • 啟用 載入 .env 和 bunfig.toml — 這些通常包含執行環境設定,部署時可能有所不同

執行環境啟用設定載入

如果您的執行檔需要在執行環境讀取 tsconfig.json 或 package.json,請使用以下標誌啟用:

terminal
# 啟用 tsconfig.json 執行環境載入
bun build --compile --compile-autoload-tsconfig ./app.ts --outfile myapp

# 啟用 package.json 執行環境載入
bun build --compile --compile-autoload-package-json ./app.ts --outfile myapp

# 同時啟用
bun build --compile --compile-autoload-tsconfig --compile-autoload-package-json ./app.ts --outfile myapp

執行環境停用設定載入

要停用 .env 或 bunfig.toml 以實現確定性執行:

terminal
# 停用 .env 載入
bun build --compile --no-compile-autoload-dotenv ./app.ts --outfile myapp

# 停用 bunfig.toml 載入
bun build --compile --no-compile-autoload-bunfig ./app.ts --outfile myapp

# 停用所有設定載入
bun build --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig ./app.ts --outfile myapp

作為 Bun CLI 使用

設定 BUN_BE_BUN=1 環境變數,可以將獨立執行檔作為 bun CLI 本身執行。該執行檔會忽略其打包的入口點,轉而提供完整的 bun CLI。

例如,考慮一個由以下指令碼編譯而成的執行檔:

terminal
echo "console.log(\"you shouldn't see this\");" > such-bun.js
bun build --compile ./such-bun.js
Text
[3ms] bundle 1 modules
[89ms] compile such-bun

通常,使用引數執行 ./such-bun 時,會執行該指令碼。

terminal
# 預設執行打包入口指令碼
./such-bun install
Text
you shouldn't see this

不過,設定 BUN_BE_BUN=1 環境變數後,該執行檔的行為會與 bun 二進位制檔案相同:

terminal
# 通過環境變數讓執行檔行為等同於 `bun` CLI
BUN_BE_BUN=1 ./such-bun install
Text
bun install v1.2.16-canary.1 (1d1db811)
Checked 63 installs across 64 packages (no changes) [5.00ms]

基於 Bun 建置的 CLI 工具可以利用此功能來安裝軟體包、打包相依項或執行其他檔案,而無需下載單獨的二進位制檔案或安裝 Bun。

全棧執行檔

--compile 標誌可以建立一個同時包含伺服器和客戶端程式碼的獨立執行檔,非常適合全棧應用。當你在伺服器程式碼中匯入 HTML 檔案時,Bun 會將前端資源(JavaScript、CSS 等)打包並嵌入執行檔中。

server.ts
import { serve } from "bun";
import index from "./index.html";

const server = serve({
  routes: {
    "/": index,
    "/api/hello": { GET: () => Response.json({ message: "Hello from API" }) },
  },
});

console.log(`Server running at http://localhost:${server.port}`);
index.html
<!DOCTYPE html>
<html>
  <head>
    <title>我的應用</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <h1>你好,世界</h1>
    <script src="./app.ts"></script>
  </body>
</html>
app.ts
console.log("Hello from the client!");
styles.css
body {
  background-color: #f0f0f0;
}

建置為單個執行檔:

terminal
bun build --compile ./server.ts --outfile myapp

生成的獨立二進位制包含:

  • 你的伺服器程式碼
  • Bun 執行環境
  • 所有前端資源(HTML、CSS、JavaScript)
  • 伺服器用到的所有 npm 包

最終生成的是一個單獨的檔案,你可以將其部署到任何地方,而無需安裝 Node.js、Bun 或任何相依項:

terminal
./myapp

Bun 會使用正確的 MIME 型別和快取標頭來提供前端資源。Bun 會將 HTML 匯入替換為清單物件,Bun.serve 會使用該物件提供預先打包的資源。

有關建置全棧應用的更多資訊,請參閱全棧指南。


Worker

使用獨立執行檔中的 Worker,需要將 Worker 入口也加入建置:

terminal
bun build --compile ./index.ts ./my-worker.ts --outfile myapp

然後在程式碼中這樣引用 Worker:

index.ts
console.log("Hello from Bun!");

// 以下方式均可:
new Worker("./my-worker.ts");
new Worker(new URL("./my-worker.ts", import.meta.url));
new Worker(new URL("./my-worker.ts", import.meta.url).href);

向獨立執行檔新增多個入口點時,Bun 會將每個入口點分別打包到執行檔中。

我們最終可能會檢測 new Worker(path) 中靜態已知的路徑,並自動打包它們。目前,你需要像前面的範例那樣,將 Worker 檔案列為入口點。

如果你使用相對路徑指向未包含在獨立執行檔中的檔案,Bun 會相對於行程當前工作目錄從磁碟載入該路徑;如果檔案不存在,則會報錯。


SQLite

使用 bun:sqlite 匯入時,支援 bun build --compile。

預設情況下,Bun 會相對於行程的當前工作目錄解析資料庫路徑。

index.ts
import db from "./my.db" with { type: "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());

這意味著,如果執行檔位於 /usr/bin/hello,且使用者的終端當前位於 /home/me/Desktop,Bun 會查詢 /home/me/Desktop/my.db。

terminal
cd /home/me/Desktop
./hello

嵌入靜態資源及檔案

獨立執行檔可以直接將檔案嵌入二進位制檔案中,因此單個執行檔就可以攜帶應用程式所需的影像、JSON 設定、模板或其他資源。

機制原理

使用 with { type: "file" } 匯入屬性來嵌入檔案:

index.ts
import icon from "./icon.png" with { type: "file" };

console.log(icon);
// 開發期間:"./icon.png"
// 編譯後:"/$bunfs/root/icon-a1b2c3d4.png"(內部路徑)

此匯入傳回路徑字串,指向嵌入檔案。建置時 Bun 會:

  1. 讀取檔案內容
  2. 將資料嵌入執行檔
  3. 將匯入替換為內部路徑(以 /$bunfs/ 為字首)

你可以通過 Bun.file() 或 Node.js 的 fs API 讀取嵌入檔案。

用 Bun.file() 讀取嵌入檔案

Bun.file() 是讀取嵌入檔案的推薦方式:

index.ts
import icon from "./icon.png" with { type: "file" };
import { file } from "bun";

// 以不同方式讀取檔案內容
const bytes = await file(icon).arrayBuffer(); // ArrayBuffer
const text = await file(icon).text(); // 字串(文本檔案)
const blob = file(icon); // Blob 物件

// 在響應中流式傳輸檔案
export default {
  fetch(req) {
    return new Response(file(icon), {
      headers: { "Content-Type": "image/png" },
    });
  },
};

用 Node.js fs 讀取嵌入檔案

嵌入檔案可以與 Node.js 檔案系統 API 一起使用:

index.ts
import icon from "./icon.png" with { type: "file" };
import config from "./config.json" with { type: "file" };
import { readFileSync, promises as fs } from "node:fs";

// 同步讀取
const iconBuffer = readFileSync(icon);

// 非同步讀取
const configData = await fs.readFile(config, "utf-8");
const parsed = JSON.parse(configData);

// 檔案狀態檢查
const stats = await fs.stat(icon);
console.log(`Icon size: ${stats.size} bytes`);

實踐範例

嵌入 JSON 設定

index.ts
import configPath from "./default-config.json" with { type: "file" };
import { file } from "bun";

// 載入內建預設設定
const defaultConfig = await file(configPath).json();

// 嘗試載入使用者設定,失敗則為空物件
const userConfig = await file("./user-config.json")
  .json()
  .catch(() => ({}));
const config = { ...defaultConfig, ...userConfig };

HTTP 伺服器中提供靜態資源

在 Bun.serve() 中使用靜態路由,可高效提供靜態檔案:

server.ts
import favicon from "./favicon.ico" with { type: "file" };
import logo from "./logo.png" with { type: "file" };
import styles from "./styles.css" with { type: "file" };
import { file, serve } from "bun";

serve({
  // Inside the compiled executable, file() on an embedded path returns an
  // in-memory Blob, which routes only accepts wrapped in a Response.
  routes: {
    "/favicon.ico": new Response(file(favicon)),
    "/logo.png": new Response(file(logo)),
    "/styles.css": new Response(file(styles)),
  },
  fetch(req) {
    return new Response("Not found", { status: 404 });
  },
});

Bun 會自動處理靜態路由的 Content-Type 標頭和快取策略。

嵌入模板檔案

index.ts
import templatePath from "./email-template.html" with { type: "file" };
import { file } from "bun";

async function sendWelcomeEmail(user: { name: string; email: string }) {
  const template = await file(templatePath).text();
  const html = template.replace("{{name}}", user.name).replace("{{email}}", user.email);

  // 傳送郵件,使用渲染後的模板...
}

嵌入二進位制檔案

index.ts
import wasmPath from "./processor.wasm" with { type: "file" };
import fontPath from "./font.ttf" with { type: "file" };
import { file } from "bun";

// 載入 WebAssembly 模組
const wasmBytes = await file(wasmPath).arrayBuffer();
const wasmModule = await WebAssembly.instantiate(wasmBytes);

// 讀取字型二進位制資料
const fontData = await file(fontPath).bytes();

將文本嵌入為字串

如果要將檔案內容作為字串使用,請使用 text 載入器。.txt 檔案預設使用此載入器。對於其他副檔名,可以新增 with { type: "text" },或傳入 --loader .md:text。

index.ts
import notes from "./notes.txt";
import readme from "./README.md" with { type: "text" };

console.log(notes); // the contents of notes.txt

在編譯後的執行檔中,文本只儲存一次,格式為 JavaScript 引擎用於字串的形式(ASCII 文本每個字元佔一個位元組,其他文本則使用 UTF-16)。執行環境會直接傳回字串,不會複製,因此大型文本匯入會佔用等同於其大小的二進位制檔案空間,並且不會在堆上產生額外副本。文本匯入是一個模組,而不是檔案:Bun.embeddedFiles 不會列出它。需要檔案路徑時,請使用 with { type: "file" }。

嵌入 SQLite 資料庫

要將 SQLite 資料庫嵌入編譯後的執行檔,請在匯入屬性中設定 type: "sqlite",並將 embed 屬性設定為 "true"。

資料庫檔案必須已存在於磁碟。然後在程式碼中匯入:

index.ts
import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" };

console.log(myEmbeddedDb.query("select * from users LIMIT 1").get());

最後編譯為獨立執行檔:

terminal
bun build --compile ./index.ts --outfile mycli

在編譯後的執行檔中,嵌入的資料庫支援讀寫。由於資料庫儲存在記憶體中,執行檔退出時,所有更改都會丟失。

嵌入 N-API 外掛

可將 .node 檔案嵌入執行檔:

index.ts
const addon = require("./addon.node");

console.log(addon.hello());

如果你使用 @mapbox/node-pre-gyp 或類似工具,請直接 require .node 檔案,否則打包將無法正確完成。

嵌入目錄

使用 --asset(或 JavaScript API 中的 compile.assets)將檔案或目錄樹嵌入執行檔,並保留其原始相對路徑。嵌入的檔案在執行環境位於 import.meta.dir 下,並且可以通過 node:fs(existsSync、statSync、readdirSync、readFileSync)和 Bun.file() 訪問。

terminal
bun build --compile ./index.ts --asset ./public --outfile myapp
index.ts
import fs from "node:fs";
import path from "node:path";

const publicDir = path.join(import.meta.dir, "public");

for (const entry of fs.readdirSync(publicDir, { withFileTypes: true })) {
  console.log(entry.name, entry.isDirectory() ? "(dir)" : fs.statSync(path.join(publicDir, entry.name)).size);
}

const html = await Bun.file(path.join(publicDir, "index.html")).text();

多次傳入 --asset 可嵌入多個目錄(例如,對於 SvelteKit 建置,可以使用 --asset ./client --asset ./prerendered)。Bun 只嵌入常規檔案;它會跳過目錄樹中的符號連結和空子目錄。

你也可以通過 with { type: "file" } 匯入屬性嵌入單個檔案;對於使用 file 載入器的檔案(影像、字型等)以及 .wasm 和 .node 檔案,也可以將它們作為額外的入口點新增。Bun 會根據 --asset-naming 重新命名匯入的資源(預設值為 [name]-[hash].[ext]):

TypeScript
import icon from "./public/assets/icon.png" with { type: "file" };

執行環境檢測獨立模式

使用 Bun.isStandaloneExecutable 來檢查當前行程是否正在從編譯後的二進位制檔案執行:

index.ts
if (Bun.isStandaloneExecutable) {
  // 從 `bun build --compile` 的輸出執行
} else {
  // 通過 `bun <file>` 執行,或作為庫使用
}

與 Bun.embeddedFiles.length > 0 不同,這種檢查不會為每個嵌入檔案分配 Blob 物件,因此即使二進位制檔案嵌入了大量資源,也可以安全地在啟動時呼叫。

列出嵌入檔案

Bun.embeddedFiles 將所有嵌入檔案以 Blob 物件的形式公開:

index.ts
import "./icon.png" with { type: "file" };
import "./data.json" with { type: "file" };
import "./template.html" with { type: "file" };
import { embeddedFiles } from "bun";

// 列出所有嵌入檔案
for (const blob of embeddedFiles) {
  console.log(`${blob.name} - ${blob.size} bytes`);
}
// 輸出範例:
//   icon-a1b2c3d4.png - 4096 bytes
//   data-e5f6g7h8.json - 256 bytes
//   template-i9j0k1l2.html - 1024 bytes

Bun.embeddedFiles 中每個元素是帶 name 屬性的 Blob:

TypeScript
embeddedFiles: ReadonlyArray<Blob>;

使用它可通過靜態路由提供所有嵌入資源:

server.ts
import "./public/favicon.ico" with { type: "file" };
import "./public/logo.png" with { type: "file" };
import "./public/styles.css" with { type: "file" };
import { embeddedFiles, serve } from "bun";

// Build static routes from all embedded files
const staticRoutes: Record<string, Response> = {};
for (const blob of embeddedFiles) {
  // Remove hash from filename: "icon-a1b2c3d4.png" -> "icon.png"
  const name = blob.name.replace(/-[a-z0-9]+\./, ".");
  // embeddedFiles are plain Blobs, which routes does not accept directly
  staticRoutes[`/${name}`] = new Response(blob);
}

serve({
  routes: staticRoutes,
  fetch(req) {
    return new Response("Not found", { status: 404 });
  },
});

內容雜湊

預設情況下,Bun 會在每個嵌入檔案的名稱後附加內容雜湊,這有助於從 URL 或 CDN 提供檔案時進行快取失效。如要保留原始名稱,請設定資源命名:

terminal
bun build --compile --asset-naming="[name].[ext]" ./index.ts

程式碼壓縮(Minification)

啟用程式碼壓縮以減少執行檔體積:

terminal
bun build --compile --minify ./index.ts --outfile myapp

Bun 使用自身壓縮器來減小程式碼體積。不過總體上 Bun 的二進位制仍然偏大,未來還會最佳化。


Windows 平臺特有標誌

在 Windows 上編譯獨立執行檔時,平臺特定選項可以自定義生成的 .exe 檔案的後設資料:

terminal
# 自定義圖示
bun build --compile --windows-icon=path/to/icon.ico ./app.ts --outfile myapp

# 隱藏控制台視窗(GUI 應用)
bun build --compile --windows-hide-console ./app.ts --outfile myapp

Windows 可用選項說明:

  • icon - 指定 .ico 圖示檔案路徑
  • hideConsole - 隱藏後臺終端視窗(GUI 應用用)
  • title - 執行檔屬性中的應用標題
  • publisher - 釋出者名稱
  • version - 版本號字串
  • description - 描述資訊
  • copyright - 版權宣告

macOS 程式碼簽名

為獨立執行檔進行程式碼簽名以消除 Gatekeeper 警告,使用 codesign 命令:

terminal
codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp

推薦附帶帶有 JIT 許可權的 entitlements.plist 檔案:

entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-executable-page-protection</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
</dict>
</plist>

使用 --entitlements 標誌來支援 JIT 許可權:

terminal
codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp

簽名後驗證:

terminal
codesign -vvv --verify ./myapp
./myapp: valid on disk
./myapp: satisfies its Designated Requirement

程式碼拆分

獨立執行檔支援程式碼拆分。結合 --compile 和 --splitting 可生成帶有執行環境動態載入程式碼拆分塊的執行檔。

terminal
bun build --compile --splitting ./src/entry.ts --outfile ./build/entry
src/entry.ts
console.log("Entrypoint loaded");
const lazy = await import("./lazy.ts");
lazy.hello();
src/lazy.ts
export function hello() {
  console.log("Lazy module loaded");
}

執行編譯結果:

terminal
./build/entry

輸出:

Text
Entrypoint loaded
Lazy module loaded

使用外掛

外掛與獨立執行檔協同工作;使用它們在建置期間轉換檔案:

build.ts
import type { BunPlugin } from "bun";

const envPlugin: BunPlugin = {
  name: "env-loader",
  setup(build) {
    build.onLoad({ filter: /\.env\.json$/ }, async args => {
      // 將 .env.json 檔案轉換成已驗證的設定物件
      const env = await Bun.file(args.path).json();

      return {
        contents: `export default ${JSON.stringify(env)};`,
        loader: "js",
      };
    });
  },
};

await Bun.build({
  entrypoints: ["./cli.ts"],
  compile: {
    outfile: "./mycli",
  },
  plugins: [envPlugin],
});

使用場景範例 — 建置時嵌入環境設定:

cli.ts
import config from "./config.env.json";

console.log(`Running in ${config.environment} mode`);
console.log(`API endpoint: ${config.apiUrl}`);

外掛可以執行任何轉換:編譯 YAML/TOML 設定、內聯 SQL 查詢、生成型別安全的 API 客戶端,或預處理模板。請參閱外掛文件。


不支援的 CLI 引數

--compile 標誌不支援以下標誌:

  • --outdir — 請改用 outfile。
  • --public-path
  • --target=node
  • --target=browser(不含 HTML 入口點 — 對於使用 .html 檔案的 --compile --target=browser,請參閱獨立 HTML)
  • --no-bundle - Bun 始終會將所有內容捆綁到執行檔中。

API 參考

Bun.build() 中的 compile 選項支援三種形式:

型別
interface BuildConfig {
  entrypoints: string[];
  compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
  // ... 其他 BuildConfig 選項(minify、sourcemap、define、plugins 等)
}

interface CompileBuildOptions {
  target?: Bun.Build.CompileTarget; // Cross-compilation target
  outfile?: string; // Output executable path
  assets?: string[]; // Files/directories to embed under import.meta.dir
  execArgv?: string[]; // Runtime arguments (process.execArgv)
  executablePath?: string; // Bun executable to use instead of downloading one for target
  autoloadTsconfig?: boolean; // Load tsconfig.json (default: false)
  autoloadPackageJson?: boolean; // Load package.json (default: false)
  autoloadDotenv?: boolean; // Load .env files (default: true)
  autoloadBunfig?: boolean; // Load bunfig.toml (default: true)
  bytecodeOrder?: string | string[] | false | null; // Order file(s) to lay the bytecode out by (requires bytecode: true)
  jitPolicy?: number; // JIT tier-up threshold scale at startup (default: 1 = normal)
  windows?: {
    icon?: string; // .ico 檔案路徑
    hideConsole?: boolean; // 隱藏控制台視窗
    title?: string; // 應用標題
    publisher?: string; // 釋出者名稱
    version?: string; // 版本字串
    description?: string; // 描述
    copyright?: string; // 版權宣告
  };
}

用法範例:

TypeScript
// 簡單布林值 - 編譯為當前平臺,輸出檔名為入口名
compile: true

// 目標字串 - 交叉編譯,輸出檔名為入口名
compile: "bun-linux-x64"

// 詳盡設定物件 - 指定輸出檔案和其他引數
compile: {
  target: "bun-linux-x64",
  outfile: "./myapp",
}

如果未指定 outfile,執行檔將以入口點命名並寫入 outdir;如果未指定 outdir,則寫入當前工作目錄。這與 bun build --compile 的行為一致。

支援的目標平臺

Bun.Build.CompileTarget
type CompileTarget =
  | "bun-darwin-x64"
  | "bun-darwin-arm64"
  | "bun-linux-x64"
  | "bun-linux-arm64"
  | "bun-linux-x64-musl"
  | "bun-linux-x64-baseline-musl"
  | "bun-linux-arm64-musl"
  | "bun-windows-x64"
  | "bun-windows-arm64";
// The "-baseline" and "-modern" suffixes are accepted for backward
// compatibility and resolve to the same x64 binary.

完整範例

build.ts
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(build) {
    // 外掛實現
  },
};

const result = await Bun.build({
  entrypoints: ["./src/cli.ts"],
  compile: {
    target: "bun-linux-x64",
    outfile: "./dist/mycli",
    execArgv: ["--smol"],
    autoloadDotenv: false,
    autoloadBunfig: false,
  },
  minify: true,
  sourcemap: "linked",
  bytecode: true,
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
    VERSION: JSON.stringify("1.0.0"),
  },
  plugins: [myPlugin],
});

if (result.success) {
  console.log("建置成功:", result.outputs[0].path);
}