独立可执行文件
使用 Bun 从 TypeScript 或 JavaScript 文件生成独立可执行文件
Bun 的打包器支持 --compile 标志,用于从 TypeScript 或 JavaScript 文件生成独立的二进制可执行文件。
bun build ./cli.ts --compile --outfile mycli
await Bun.build({
entrypoints: ["./cli.ts"],
compile: {
outfile: "./mycli",
},
});
console.log("Hello world!");
Bun 会将 cli.ts 打包为可直接运行的可执行文件:
./mycli
Hello world!
Bun 会将所有导入的文件和包以及一份 Bun 运行时副本打包到可执行文件中。所有内置的 Bun 和 Node.js API 均受支持。
跨平台交叉编译
使用 --target 标志,可以将独立可执行文件编译为与运行 bun build 的机器不同的操作系统、架构或 Bun 版本。
构建 Linux x64 版本(大多数服务器):
bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
target: "bun-linux-x64",
outfile: "./myapp",
},
});
构建 Linux ARM64 版本(例如 Graviton 或 Raspberry Pi):
# 注意:如果未指定架构,默认架构为 x64。
bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
target: "bun-linux-arm64",
outfile: "./myapp",
},
});
构建 Windows x64 版本:
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
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
target: "bun-windows-x64",
outfile: "./myapp", // 自动加上 .exe 后缀
},
});
为 Windows arm64 构建:
bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp
# 注意:如果未提供 .exe 扩展名,Bun 会自动为 Windows 可执行文件添加该扩展名
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
target: "bun-windows-arm64",
outfile: "./myapp", // 自动加上 .exe
},
});
构建 macOS arm64 版本:
bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
target: "bun-darwin-arm64",
outfile: "./myapp",
},
});
构建 macOS x64 版本:
bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
target: "bun-darwin-x64",
outfile: "./myapp",
},
});
支持的目标平台
--target 值的各个部分可以按任意顺序出现,只要它们使用 - 分隔即可。
| --target | 操作系统 | 架构 | Libc |
|---|---|---|---|
| bun-linux-x64 | Linux | x64 | glibc |
| bun-linux-arm64 | Linux | arm64 | glibc |
| bun-windows-x64 | Windows | x64 | - |
| bun-windows-arm64 | Windows | arm64 | - |
| bun-darwin-x64 | macOS | x64 | - |
| bun-darwin-arm64 | macOS | arm64 | - |
| bun-linux-x64-musl | Linux | x64 | musl |
| bun-linux-arm64-musl | Linux | arm64 | musl |
编译时常量
使用 --define 标志可将编译时常量注入可执行文件,例如版本号、构建时间戳或配置值:
bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli
await Bun.build({
entrypoints: ["./src/cli.ts"],
compile: {
outfile: "./mycli",
},
define: {
BUILD_VERSION: JSON.stringify("1.2.3"),
BUILD_TIME: JSON.stringify("2024-01-15T10:30:00Z"),
},
});
Bun 会在构建时将这些常量内联到二进制文件中,因此它们不会产生任何运行时开销,并且能够实现死代码消除。
生产环境部署
编译后的可执行文件降低内存使用并提升 Bun 启动速度。
通常情况下,Bun 会在 import 和 require 时读取并转译 JavaScript 和 TypeScript 文件。这正是 Bun 能够“开箱即用”的原因之一,但这并非没有代价:从磁盘读取文件、解析路径、解析代码、转译以及打印源代码都会消耗时间和内存。
编译后的可执行文件将这些开销从运行时转移到了构建时。
部署到生产环境推荐做法:
bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
outfile: "./myapp",
},
minify: true,
sourcemap: "linked",
});
字节码编译
为提升启动速度,启用字节码编译:
bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
outfile: "./myapp",
},
minify: true,
sourcemap: "linked",
bytecode: true,
});
使用字节码编译,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。
# 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 中,记录配置文件并在同一任务中使用它:
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:
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);程序应在进入交互状态后调用此函数:
await Bun.build({
entrypoints: ["./app.ts"],
compile: { outfile: "./myapp", jitPolicy: 8 },
bytecode: true,
});
await renderFirstScreen();
Bun.unsafe.setJITPolicy(1); // back to the normal JIT policy
嵌入运行时参数
--compile-exec-argv="args" - 嵌入运行时参数,可在运行时通过 process.execArgv 获取:
bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp
await Bun.build({
entrypoints: ["./app.ts"],
compile: {
execArgv: ["--smol", "--user-agent=MyBot"],
outfile: "./myapp",
},
});
// 在编译后的程序中
console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"]
通过 BUN_OPTIONS 传入运行时参数
独立可执行文件会读取 BUN_OPTIONS 环境变量,因此你可以无需重新编译即可传入运行时标志:
# 在编译后的可执行文件上启用 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,请使用以下标志启用:
# 启用 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 以实现确定性执行:
# 禁用 .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
await Bun.build({
entrypoints: ["./app.ts"],
compile: {
// tsconfig.json 和 package.json 默认禁用
autoloadTsconfig: true, // 启用 tsconfig.json 加载
autoloadPackageJson: true, // 启用 package.json 加载
// .env 和 bunfig.toml 默认启用
autoloadDotenv: false, // 禁用 .env 加载
autoloadBunfig: false, // 禁用 bunfig.toml 加载
outfile: "./myapp",
},
});
作为 Bun CLI 使用
设置 BUN_BE_BUN=1 环境变量,可以将独立可执行文件作为 bun CLI 本身运行。该可执行文件会忽略其打包的入口点,转而提供完整的 bun CLI。
例如,考虑一个由以下脚本编译而成的可执行文件:
echo "console.log(\"you shouldn't see this\");" > such-bun.js
bun build --compile ./such-bun.js
[3ms] bundle 1 modules
[89ms] compile such-bun
通常,使用参数运行 ./such-bun 时,会执行该脚本。
# 默认执行打包入口脚本
./such-bun install
you shouldn't see this
不过,设置 BUN_BE_BUN=1 环境变量后,该可执行文件的行为会与 bun 二进制文件相同:
# 通过环境变量让可执行文件行为等同于 `bun` CLI
BUN_BE_BUN=1 ./such-bun install
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 等)打包并嵌入可执行文件中。
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}`);
<!DOCTYPE html>
<html>
<head>
<title>我的应用</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<h1>你好,世界</h1>
<script src="./app.ts"></script>
</body>
</html>
console.log("Hello from the client!");
body {
background-color: #f0f0f0;
}
构建为单个可执行文件:
bun build --compile ./server.ts --outfile myapp
await Bun.build({
entrypoints: ["./server.ts"],
compile: {
outfile: "./myapp",
},
});
生成的独立二进制包含:
- 你的服务器代码
- Bun 运行时
- 所有前端资源(HTML、CSS、JavaScript)
- 服务器用到的所有 npm 包
最终生成的是一个单独的文件,你可以将其部署到任何地方,而无需安装 Node.js、Bun 或任何依赖项:
./myapp
Bun 会使用正确的 MIME 类型和缓存标头来提供前端资源。Bun 会将 HTML 导入替换为清单对象,Bun.serve 会使用该对象提供预先打包的资源。
有关构建全栈应用的更多信息,请参阅全栈指南。
Worker
使用独立可执行文件中的 Worker,需要将 Worker 入口也加入构建:
bun build --compile ./index.ts ./my-worker.ts --outfile myapp
await Bun.build({
entrypoints: ["./index.ts", "./my-worker.ts"],
compile: {
outfile: "./myapp",
},
});
然后在代码中这样引用 Worker:
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 会相对于进程的当前工作目录解析数据库路径。
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。
cd /home/me/Desktop
./hello
嵌入静态资源及文件
独立可执行文件可以直接将文件嵌入二进制文件中,因此单个可执行文件就可以携带应用程序所需的图像、JSON 配置、模板或其他资源。
机制原理
使用 with { type: "file" } 导入属性来嵌入文件:
import icon from "./icon.png" with { type: "file" };
console.log(icon);
// 开发期间:"./icon.png"
// 编译后:"/$bunfs/root/icon-a1b2c3d4.png"(内部路径)
此导入返回路径字符串,指向嵌入文件。构建时 Bun 会:
- 读取文件内容
- 将数据嵌入可执行文件
- 将导入替换为内部路径(以
/$bunfs/为前缀)
你可以通过 Bun.file() 或 Node.js 的 fs API 读取嵌入文件。
用 Bun.file() 读取嵌入文件
Bun.file() 是读取嵌入文件的推荐方式:
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 一起使用:
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 配置
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() 中使用静态路由,可高效提供静态文件:
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 标头和缓存策略。
嵌入模板文件
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);
// 发送邮件,使用渲染后的模板...
}
嵌入二进制文件
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。
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"。
数据库文件必须已存在于磁盘。然后在代码中导入:
import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" };
console.log(myEmbeddedDb.query("select * from users LIMIT 1").get());
最后编译为独立可执行文件:
bun build --compile ./index.ts --outfile mycli
在编译后的可执行文件中,嵌入的数据库支持读写。由于数据库存储在内存中,可执行文件退出时,所有更改都会丢失。
嵌入 N-API 插件
可将 .node 文件嵌入可执行文件:
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() 访问。
bun build --compile ./index.ts --asset ./public --outfile myapp
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
outfile: "./myapp",
assets: ["./public"],
},
});
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]):
import icon from "./public/assets/icon.png" with { type: "file" };
运行时检测独立模式
使用 Bun.isStandaloneExecutable 来检查当前进程是否正在从编译后的二进制文件运行:
if (Bun.isStandaloneExecutable) {
// 从 `bun build --compile` 的输出运行
} else {
// 通过 `bun <file>` 运行,或作为库使用
}
与 Bun.embeddedFiles.length > 0 不同,这种检查不会为每个嵌入文件分配 Blob 对象,因此即使二进制文件嵌入了大量资源,也可以安全地在启动时调用。
列出嵌入文件
Bun.embeddedFiles 将所有嵌入文件以 Blob 对象的形式公开:
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:
embeddedFiles: ReadonlyArray<Blob>;
使用它可通过静态路由提供所有嵌入资源:
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 提供文件时进行缓存失效。如要保留原始名称,请配置资源命名:
bun build --compile --asset-naming="[name].[ext]" ./index.ts
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
outfile: "./myapp",
},
naming: {
asset: "[name].[ext]",
},
});
代码压缩(Minification)
启用代码压缩以减少可执行文件体积:
bun build --compile --minify ./index.ts --outfile myapp
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
outfile: "./myapp",
},
minify: true, // 启用全部压缩
});
// 或更细粒度控制:
await Bun.build({
entrypoints: ["./index.ts"],
compile: {
outfile: "./myapp",
},
minify: {
whitespace: true,
syntax: true,
identifiers: true,
},
});
Bun 使用自身压缩器来减小代码体积。不过总体上 Bun 的二进制仍然偏大,未来还会优化。
Windows 平台特有标志
在 Windows 上编译独立可执行文件时,平台特定选项可以自定义生成的 .exe 文件的元数据:
# 自定义图标
bun build --compile --windows-icon=path/to/icon.ico ./app.ts --outfile myapp
# 隐藏控制台窗口(GUI 应用)
bun build --compile --windows-hide-console ./app.ts --outfile myapp
await Bun.build({
entrypoints: ["./app.ts"],
compile: {
outfile: "./myapp",
windows: {
icon: "./path/to/icon.ico",
hideConsole: true,
// 额外的 Windows 元数据:
title: "My Application",
publisher: "My Company",
version: "1.0.0",
description: "A standalone Windows application",
copyright: "Copyright 2024",
},
},
});
Windows 可用选项说明:
icon- 指定.ico图标文件路径hideConsole- 隐藏后台终端窗口(GUI 应用用)title- 可执行文件属性中的应用标题publisher- 发布者名称version- 版本号字符串description- 描述信息copyright- 版权声明
macOS 代码签名
为独立可执行文件进行代码签名以消除 Gatekeeper 警告,使用 codesign 命令:
codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp
推荐附带带有 JIT 权限的 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 权限:
codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp
签名后验证:
codesign -vvv --verify ./myapp
./myapp: valid on disk
./myapp: satisfies its Designated Requirement
代码拆分
独立可执行文件支持代码拆分。结合 --compile 和 --splitting 可生成带有运行时动态加载代码拆分块的可执行文件。
bun build --compile --splitting ./src/entry.ts --outfile ./build/entry
await Bun.build({
entrypoints: ["./src/entry.ts"],
compile: true,
splitting: true,
outdir: "./build",
});
console.log("Entrypoint loaded");
const lazy = await import("./lazy.ts");
lazy.hello();
export function hello() {
console.log("Lazy module loaded");
}
运行编译结果:
./build/entry
输出:
Entrypoint loaded
Lazy module loaded
使用插件
插件与独立可执行文件协同工作;使用它们在构建期间转换文件:
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],
});
使用场景示例 — 构建时嵌入环境配置:
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; // 版权声明
};
}
用法示例:
// 简单布尔值 - 编译为当前平台,输出文件名为入口名
compile: true
// 目标字符串 - 交叉编译,输出文件名为入口名
compile: "bun-linux-x64"
// 详尽配置对象 - 指定输出文件和其他参数
compile: {
target: "bun-linux-x64",
outfile: "./myapp",
}
如果未指定 outfile,可执行文件将以入口点命名并写入 outdir;如果未指定 outdir,则写入当前工作目录。这与 bun build --compile 的行为一致。
支持的目标平台
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.
完整示例
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);
}