Bun

單一檔案可執行檔

Bun 的 bundler 實作了 --compile 標記,可從 TypeScript 或 JavaScript 檔案產生獨立二進制檔案。

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

這會將 cli.ts 捆綁到可以直接執行的可執行檔中

$ ./mycli
Hello world!

所有匯入的檔案和套件都會與 Bun 執行時期的副本一起捆綁到可執行檔中。所有內建的 Bun 和 Node.js API 都受到支援。

跨平台編譯

--target 標記可讓您為不同的作業系統、架構或 Bun 版本編譯獨立可執行檔,而不是執行 bun build 的機器。

為 Linux x64 (大多數伺服器) 建置

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

# To support CPUs from before 2013, use the baseline version (nehalem)
bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp

# To explicitly only support CPUs from 2013 and later, use the modern version (haswell)
# modern is faster, but baseline is more compatible.
bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp

為 Linux ARM64 (例如 Graviton 或 Raspberry Pi) 建置

# Note: the default architecture is x64 if no architecture is specified.
bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp

為 Windows x64 建置

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

# To support CPUs from before 2013, use the baseline version (nehalem)
bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp

# To explicitly only support CPUs from 2013 and later, use the modern version (haswell)
bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp

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

為 macOS arm64 建置

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

為 macOS x64 建置

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

支援的目標

--target 標記的順序並不重要,只要它們以 - 分隔即可。

--target作業系統架構ModernBaselineLibc
bun-linux-x64Linuxx64glibc
bun-linux-arm64Linuxarm64N/Aglibc
bun-windows-x64Windowsx64-
bun-windows-arm64Windowsarm64-
bun-darwin-x64macOSx64-
bun-darwin-arm64macOSarm64N/A-
bun-linux-x64-muslLinuxx64musl
bun-linux-arm64-muslLinuxarm64N/Amusl

在 x64 平台上,Bun 使用 SIMD 優化,這需要支援 AVX2 指令的現代 CPU。Bun 的 -baseline 建置適用於不支援這些優化的舊 CPU。通常,當您安裝 Bun 時,我們會自動偵測要使用的版本,但在跨平台編譯時,這可能比較困難,因為您可能不知道目標 CPU。在 Darwin x64 上,您通常不需要擔心它,但它與 Windows x64 和 Linux x64 相關。如果您或您的使用者看到 "Illegal instruction" 錯誤,您可能需要使用 baseline 版本。

部署到生產環境

編譯後的可執行檔能減少記憶體用量並改善 Bun 的啟動時間。

一般來說,Bun 會在 importrequire 時讀取並轉譯 JavaScript 和 TypeScript 檔案。這是 Bun 如此「開箱即用」的部分原因,但並非沒有代價。讀取硬碟檔案、解析檔案路徑、剖析、轉譯和輸出原始碼都需要時間和記憶體。

透過編譯後的可執行檔,您可以將這些成本從執行時期轉移到建置時期。

當部署到生產環境時,我們建議以下作法

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

位元組碼編譯

為了改善啟動時間,請啟用位元組碼編譯

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

使用位元組碼編譯,tsc 啟動速度快了 2 倍

位元組碼編譯將大型輸入檔案的剖析開銷從執行時期轉移到打包時間。您的應用程式啟動速度更快,但 bun build 命令會稍微變慢。它不會混淆原始碼。

實驗性功能: 位元組碼編譯是 Bun v1.1.30 中引入的實驗性功能。僅支援 cjs 格式(這表示不支援頂層 await)。如果您遇到任何問題,請告訴我們!

這些標記有什麼作用?

--minify 參數會最佳化轉譯後輸出程式碼的大小。如果您的應用程式很大,這可以節省數 MB 的空間。對於較小的應用程式,它可能仍然可以稍微改善啟動時間。

--sourcemap 參數會嵌入使用 zstd 壓縮的 source map,以便錯誤和堆疊追蹤指向其原始位置,而不是轉譯後的位置。當發生錯誤時,Bun 會自動解壓縮並解析 source map。

--bytecode 參數啟用位元組碼編譯。每次您在 Bun 中執行 JavaScript 程式碼時,JavaScriptCore(引擎)都會將您的原始碼編譯成位元組碼。我們可以將此剖析工作從執行時期轉移到打包時間,從而節省您的啟動時間。

Worker

若要在獨立可執行檔中使用 worker,請將 worker 的進入點新增到 CLI 參數中

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

然後,在您的程式碼中參考 worker

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

// Any of these will work:
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 v1.1.25 開始,當您將多個進入點新增到獨立可執行檔時,它們將會被分別打包到可執行檔中。

未來,我們可能會自動偵測 new Worker(path) 中靜態已知的路徑用法,然後將它們打包到可執行檔中,但目前,您需要像上面的範例一樣手動將其新增到 shell 命令中。

如果您使用相對於未包含在獨立可執行檔中的檔案的相對路徑,它將嘗試從磁碟載入相對於進程當前工作目錄的路徑(如果路徑不存在,則會發生錯誤)。

SQLite

您可以將 bun:sqlite 匯入與 bun build --compile 一起使用。

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

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

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

這表示如果可執行檔位於 /usr/bin/hello,使用者的終端機位於 /home/me/Desktop,它將會尋找 /home/me/Desktop/my.db

$ cd /home/me/Desktop
$ ./hello

嵌入 assets & 檔案

獨立可執行檔支援嵌入檔案。

若要使用 bun build --compile 將檔案嵌入到可執行檔中,請在您的程式碼中匯入該檔案

// this becomes an internal file path
import icon from "./icon.png" with { type: "file" };
import { file } from "bun";

export default {
  fetch(req) {
    // Embedded files can be streamed from Response objects
    return new Response(file(icon));
  },
};

可以使用 Bun.file 的函數或 Node.js fs.readFile 函數(在 "node:fs" 中)讀取嵌入的檔案。

例如,若要讀取嵌入檔案的內容

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

const bytes = await file(icon).arrayBuffer();
// await fs.promises.readFile(icon)
// fs.readFileSync(icon)

嵌入 SQLite 資料庫

如果您的應用程式想要嵌入 SQLite 資料庫,請在 import 屬性中設定 type: "sqlite",並將 embed 屬性設定為 "true"

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

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

此資料庫是可讀寫的,但所有變更都會在可執行檔結束時遺失(因為它儲存在記憶體中)。

嵌入 N-API 插件

從 Bun v1.0.23 開始,您可以將 .node 檔案嵌入到可執行檔中。

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

console.log(addon.hello());

遺憾的是,如果您使用 @mapbox/node-pre-gyp 或其他類似工具,您需要確保直接 require .node 檔案,否則它將無法正確打包。

嵌入目錄

若要使用 bun build --compile 嵌入目錄,請在您的 bun build 命令中使用 shell glob

bun build --compile ./index.ts ./public/**/*.png

然後,您可以在程式碼中參考這些檔案

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

export default {
  fetch(req) {
    // Embedded files can be streamed from Response objects
    return new Response(file(icon));
  },
};

這實際上是一個權宜之計,我們期望未來透過更直接的 API 來改善這一點。

列出嵌入的檔案

若要取得所有嵌入檔案的列表,請使用 Bun.embeddedFiles

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

console.log(embeddedFiles[0].name); // `icon-${hash}.png`

Bun.embeddedFiles 會傳回一個 Blob 物件陣列,您可以使用這些物件來取得檔案的大小、內容和其他屬性。

embeddedFiles: Blob[]

嵌入檔案的列表不包含打包的原始碼,例如 .ts.js 檔案。

內容雜湊

預設情況下,嵌入檔案的名稱會附加內容雜湊。這對於您想要從 URL 或 CDN 提供檔案並減少快取失效問題的情況很有用。但有時,這是出乎意料的,您可能想要原始名稱。

若要停用內容雜湊,請將 --asset-naming 傳遞給 bun build --compile,如下所示

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

最小化

若要稍微縮減可執行檔的大小,請將 --minify 傳遞給 bun build --compile。這會使用 Bun 的最小化器來縮減程式碼大小。不過總體而言,Bun 的二進制檔案仍然太大,我們需要使其更小。

Windows 專用標記

在 Windows 上編譯獨立可執行檔時,可以使用兩個平台專用選項來自訂產生的 .exe 檔案上的中繼資料

  • --windows-icon=path/to/icon.ico 自訂可執行檔圖示。
  • --windows-hide-console 停用背景終端機,可用於不需要 TTY 的應用程式。

這些標記目前在跨平台編譯時無法使用,因為它們依賴 Windows API。

macOS 上的程式碼簽署

若要在 macOS 上程式碼簽署獨立可執行檔(以修正 Gatekeeper 警告),請使用 codesign 命令。

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>

若要使用 JIT 支援進行程式碼簽署,請將 --entitlements 標記傳遞給 codesign

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

程式碼簽署後,驗證可執行檔

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

程式碼簽署支援需要 Bun v1.2.4 或更新版本。

不支援的 CLI 參數

目前,--compile 標記一次只能接受單個進入點,且不支援以下標記

  • --outdir — 請改用 outfile
  • --splitting
  • --public-path
  • --target=node--target=browser
  • --format - 始終輸出二進制可執行檔。在內部,它幾乎是 esm。
  • --no-bundle - 我們始終將所有內容打包到可執行檔中。