Skip to content

Bun 的打包器实现了 --compile 标志,用于从 TypeScript 或 JavaScript 文件生成独立二进制文件。

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

这将 cli.ts 打包为一个可直接执行的可执行文件:

bash
./mycli
txt
Hello world!

所有导入的文件和包都与 Bun 运行时的副本一起打包到可执行文件中。所有内置的 Bun 和 Node.js API 都受支持。


跨平台编译

--target 标志允许您为不同于运行 bun build 的机器的操作系统、架构或 Bun 版本编译独立可执行文件。

为 Linux x64(大多数服务器)构建:

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

# 要支持 2013 年之前的 CPU,使用 baseline 版本(nehalem)
bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp

# 要仅支持 2013 年及之后的 CPU,使用 modern 版本(haswell)
# modern 更快,但 baseline 更兼容。
bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp

为 Linux ARM64(如 Graviton 或 Raspberry Pi)构建:

bash
# 注意:如果未指定架构,默认为 x64。
bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp

为 Windows x64 构建:

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

# 要支持 2013 年之前的 CPU,使用 baseline 版本(nehalem)
bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp

# 要仅支持 2013 年及之后的 CPU,使用 modern 版本(haswell)
bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp

# 注意:如果未提供 .exe 扩展名,Bun 会自动为 Windows 可执行文件添加它

为 macOS arm64 构建:

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

为 macOS x64 构建:

bash
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

构建时常量

使用 --define 标志将构建时常量注入到您的可执行文件中,如版本号、构建时间戳或配置值:

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

这些常量直接嵌入到编译后的二进制文件中,提供零运行时开销并支持死代码消除优化。

NOTE

有关综合示例和高级模式,请参阅 [构建时常量指南](/guides/runtime/build-time-constants)。

部署到生产环境

编译后的可执行文件可减少内存使用并提高 Bun 的启动时间。

通常,Bun 在 importrequire 时读取并转译 JavaScript 和 TypeScript 文件。这是让 Bun 的许多功能"开箱即用"的部分原因,但它不是免费的。从磁盘读取文件、解析文件路径、解析、转译和打印源代码需要时间和内存。

使用编译后的可执行文件,您可以将该成本从运行时转移到构建时。

部署到生产环境时,我们推荐以下内容:

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

字节码编译

要提高启动时间,启用字节码编译:

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

使用字节码编译,tsc 启动速度提高 2 倍:

字节码编译将大型输入文件的解析开销从运行时移动到打包时间。您的应用程序启动更快,以 bun build 命令稍慢为代价。它不会混淆源代码。

这些标志的作用是什么?

--minify 参数优化转译输出代码的大小。如果您有大型应用程序,这可以节省数兆字节的空间。对于较小的应用程序,它可能仍然会稍微改善启动时间。

--sourcemap 参数嵌入使用 zstd 压缩的 sourcemap,因此错误和堆栈跟踪指向其原始位置而不是转译位置。当发生错误时,Bun 会自动解压缩和解析 sourcemap。

--bytecode 参数启用字节码编译。每次在 Bun 中运行 JavaScript 代码时,JavaScriptCore(引擎)都会将源代码编译为字节码。我们可以将这项解析工作从运行时移动到打包时间,为您节省启动时间。


嵌入运行时参数

--compile-exec-argv="args" - 嵌入可通过 process.execArgv 使用的运行时参数:

bash
bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp
ts
// 在编译后的应用中
console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"]

禁用自动配置加载

默认情况下,独立可执行文件在运行可执行文件的目录中查找 .envbunfig.toml 文件。您可以在构建时禁用此行为,以实现确定性执行,无论用户的工作目录如何。

bash
# 禁用 .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

您也可以通过 JavaScript API 配置此功能:

ts
await Bun.build({
  entrypoints: ["./app.ts"],
  compile: {
    autoloadDotenv: false, // 禁用 .env 加载
    autoloadBunfig: false, // 禁用 bunfig.toml 加载
  },
});

作为 Bun CLI 运行

NOTE

Bun v1.2.16 新增

通过设置 BUN_BE_BUN=1 环境变量,您可以将独立可执行文件作为 bun CLI 本身运行。设置此变量时,可执行文件将忽略其捆绑的入口点,而是暴露 Bun CLI 的所有功能。

例如,考虑从简单脚本编译的可执行文件:

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

通常,使用参数运行 ./such-bun 将执行脚本。

bash
# 可执行文件默认运行其自己的入口点
./such-bun install
txt
you shouldn't see this

但是,使用 BUN_BE_BUN=1 环境变量,它的作用就像 bun 二进制文件:

bash
# 使用环境变量,可执行文件的作用像 `bun` CLI
BUN_BE_BUN=1 ./such-bun install
txt
bun install v1.2.16-canary.1 (1d1db811)
Checked 63 installs across 64 packages (no changes) [5.00ms]

这对于在 Bun 之上构建可能需要安装包、打包依赖项、运行不同或本地文件等的 CLI 工具很有用,而无需下载单独的二进制文件或安装 bun。


全栈可执行文件

NOTE

Bun v1.2.17 新增

Bun 的 --compile 标志可以创建包含服务器和客户端代码的独立可执行文件,使其非常适合全栈应用程序。当您在服务器代码中导入 HTML 文件时,Bun 会自动打包所有前端资源(JavaScript、CSS 等)并将它们嵌入到可执行文件中。当 Bun 在服务器上看到 HTML 导入时,它会启动前端打包过程来打包 JavaScript、CSS 和其他资源。

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}`);
html
<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <h1>Hello World</h1>
    <script src="./app.ts"></script>
  </body>
</html>
ts
console.log("Hello from the client!");
css
body {
  background-color: #f0f0f0;
}

要将此构建为单个可执行文件:

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

这将创建一个自包含的二进制文件,包括:

  • 您的服务器代码
  • Bun 运行时
  • 所有前端资源(HTML、CSS、JavaScript)
  • 服务器使用的任何 npm 包

结果是单个文件,可以在任何地方部署,无需安装 Node.js、Bun 或任何依赖项。只需运行:

bash
./myapp

Bun 自动处理使用适当的 MIME 类型和缓存头提供前端资源。HTML 导入被替换为清单对象,Bun.serve 使用该对象有效地提供预打包资源。

有关使用 Bun 构建全栈应用程序的更多详细信息,请参阅 全栈指南


Worker

要在独立可执行文件中使用 worker,将 worker 的入口点添加到 CLI 参数:

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

然后,在您的代码中引用 worker:

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);

当您将多个入口点添加到独立可执行文件时,它们将分别打包到可执行文件中。

将来,我们可能会自动检测 new Worker(path) 中静态已知路径的使用,然后将它们打包到可执行文件中,但目前,您需要像上面的示例一样手动将其添加到 shell 命令中。

如果您使用相对路径引用未包含在独立可执行文件中的文件,它将尝试从进程当前工作目录相对于磁盘加载该路径(如果不存在则出错)。


SQLite

您可以在 bun build --compile 中使用 bun:sqlite 导入。

默认情况下,数据库相对于进程的当前工作目录解析。

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,它将查找 /home/me/Desktop/my.db

bash
cd /home/me/Desktop
./hello

嵌入资源和文件

独立可执行文件支持嵌入文件。

要使用 bun build --compile 将文件嵌入到可执行文件中,请在您的代码中导入文件。

ts
// 这成为内部文件路径
import icon from "./icon.png" with { type: "file" };
import { file } from "bun";

export default {
  fetch(req) {
    // 嵌入的文件可以从 Response 对象流式传输
    return new Response(file(icon));
  },
};

嵌入的文件可以使用 Bun.file 的函数或 Node.js fs.readFile 函数(在 "node:fs" 中)读取。

例如,要读取嵌入文件的内容:

ts
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 数据库,请在导入属性中设置 type: "sqlite"embed 属性为 "true"

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

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

此数据库是可读写的,但所有更改在可执行文件退出时都会丢失(因为它存储在内存中)。

嵌入 N-API 插件

您可以将 .node 文件嵌入到可执行文件中。

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

console.log(addon.hello());

不幸的是,如果您使用 @mapbox/node-pre-gyp 或其他类似工具,您需要确保直接 require .node 文件,否则它将无法正确打包。

嵌入目录

要使用 bun build --compile 嵌入目录,请在 bun build 命令中使用 shell glob:

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

然后,您可以在代码中引用文件:

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

export default {
  fetch(req) {
    // 嵌入的文件可以从 Response 对象流式传输
    return new Response(file(icon));
  },
};

老实说,这是一种变通方法,我们期望将来通过更直接的 API 来改进这一点。

列出嵌入的文件

要获取所有嵌入文件的列表,请使用 Bun.embeddedFiles

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

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

Bun.embeddedFiles 返回 Blob 对象数组,您可以使用它们获取文件的大小、内容和其他属性。

ts
embeddedFiles: Blob[]

嵌入文件列表不包括捆绑的源代码,如 .ts.js 文件。

内容哈希

默认情况下,嵌入的文件名称附加了内容哈希。这对于想要从 URL 或 CDN 提供文件并希望减少缓存失效问题的情况很有用。但有时,这是意外的,您可能想要原始名称:

要禁用内容哈希,将 --asset-naming 传递给 bun build --compile,如下所示:

bash
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 的应用程序。

macOS 上的代码签名

要在 macOS 上对独立可执行文件进行代码签名(修复 Gatekeeper 警告),请使用 codesign 命令。

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

我们建议包含具有 JIT 权限的 entitlements.plist 文件。

xml
<?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

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

代码签名后,验证可执行文件:

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

代码分割

独立可执行文件支持代码分割。使用 --compile--splitting 创建在运行时加载代码分割块的可执行文件。

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

不支持的 CLI 参数

目前,--compile 标志一次只能接受单个入口点,不支持以下标志:

  • --outdir — 改用 outfile(除非与 --splitting 一起使用)。
  • --public-path
  • --target=node--target=browser
  • --no-bundle - 我们总是将所有内容打包到可执行文件中。

Bun学习网由www.bunjs.com.cn整理维护