我们平时开发 JavaScript 项目,图片处理要装 sharp,浏览器自动化要装 Playwright,定时任务要装 node-cron,终端交互还要装 node-pty。
Bun 1.4 继续做一件事,把这些常用能力直接放进 Bun 二进制里。
官方更新文章:bun.sh/blog/bun-v1...。
先说结论。
Bun 1.4 最值得关注的不是某一个 API,而是它开始更像一个完整的 JavaScript 工具箱。
这次更新覆盖运行时、HTTP 服务、包管理器、测试、构建器、性能、安全和平台支持。
配置解析、日志格式和压缩这类小依赖,也开始有了内置替代。
文章中的一部分功能在 Bun 1.3.x 已经出现,Bun 1.4 继续增强了它们。
所以文中会标出 1.4 新增 和 1.4 增强,避免把版本时间线混在一起。
功能总览
| 分类 | 功能 | 我的判断 |
|---|---|---|
| 运行时 | Bun.Image |
有用,适合图片上传和缩略图 |
| 运行时 | Bun.WebView |
有趣又有用,适合浏览器自动化 |
| 运行时 | Bun.markdown |
有用,适合 Markdown 渲染 |
| 运行时 | Bun.cron |
有用,适合定时任务 |
| 运行时 | Bun.Terminal |
有用,适合终端和 PTY 工具 |
| 运行时 | JSON5、JSONL、JSONC、XML、TOML | 有用,配置和日志解析不用装包 |
| 运行时 | 压缩流、textStream()、Bun.Archive |
有用,流式压缩与 tar 处理 |
| 运行时 | Bun.serve() 静态目录 |
重要,减少 Web 服务依赖 |
| HTTP | 请求压缩 | 有用,适合大 JSON 上传 |
| HTTP | 代理鉴权和 TLS 会话复用 | 有用,内网代理和高频连接 |
| HTTP | 流式响应背压 | 重要,慢客户端不再拖垮内存 |
| HTTP | HTTP/2 和 HTTP/3 | 有趣,目前仍是实验功能 |
| 工程 | bun run --parallel |
有用,适合 Monorepo |
| 工程 | 全局虚拟存储 | 重要,适合 CI 和多项目开发 |
| 工程 | bun pm diff |
有用,升级依赖前先看差异 |
| 工程 | bun update 和嵌套 overrides |
有用,依赖治理更精细 |
| 测试 | 并行、隔离、分片、受影响测试 | 重要,适合大型测试集 |
| 构建 | React Compiler | 重要,减少手动优化 |
| 构建 | bun:bundle 特性开关 |
有用,构建时移除死代码 |
| 构建 | 内存文件和单文件 HTML | 有趣,适合代码生成和离线分发 |
| 构建 | ESM 字节码和更快的代码分割 | 有用,单文件分发更完整 |
| 性能 | Rust 重写和 JavaScriptCore 优化 | 重要,生产资源消耗下降 |
| 生产 | memoryPressure 低内存通知 |
有用,OOM 前主动释放内存 |
| 安全 | TLS、HTTP 解析和 tar 包加固 | 重要,建议所有项目升级 |
| 平台 | Windows ARM64、FreeBSD、实验性 Android | 有用,平台覆盖更广 |
开始升级
先升级到 Bun 1.4。
bash
bun upgrade
bun --version
然后在项目根目录重新安装依赖。
bash
bun install
如果项目使用了原生扩展、Monorepo 或自定义 TLS,建议先看文末的升级注意事项。
Bun.Image:图片处理内置化
版本:1.3.14 提供,1.4 收录并继续完善
Bun.Image 是 Bun 内置的图片处理库。
它可以完成解码、缩放、旋转和重新编码,支持 JPEG、PNG、WebP、GIF 和 BMP。
在 macOS 和 Windows 上,HEIC、AVIF 和 TIFF 也可以使用。
以前处理缩略图,通常要安装 sharp 和对应的 native addon。
现在可以直接写在 Bun 项目里。
ts
await Bun.file("photo.jpg").image().resize(1024, 1024, { fit: "inside" }).rotate(90).webp({ quality: 85 }).write("thumb.webp");
这段代码会读取 photo.jpg,把图片限制在 1024×1024 以内,旋转 90 度,再输出为质量为 85 的 WebP。
处理上传文件时,也可以直接返回 HTTP 响应。
ts
Bun.serve({
async fetch(req) {
const form = await req.formData();
const upload = form.get("file");
if (!(upload instanceof File)) {
return new Response("file is required", { status: 400 });
}
const image = new Bun.Image(upload).resize(200).jpeg();
return new Response(image, {
headers: { "Content-Type": "image/jpeg" }
});
}
});
官方文章给出的基准是,1080p PNG 缩放成 400×400 JPEG 时,Bun.Image 比 sharp 快 1.38 倍。
这个数字是官方基准,不代表所有图片和机器都能得到相同结果。
Bun.WebView:不用 Playwright 也能驱动浏览器
版本:1.3.12 提供,1.4 增强
Bun.WebView 是内置的无头浏览器自动化 API。
它能打开网页、点击元素、执行 JavaScript、截图和发送 CDP 命令。
最有意思的地方是,点击和滚动是真实的用户输入,页面能观察到 event.isTrusted === true。
ts
await using view = new Bun.WebView({ width: 800, height: 600 });
await view.navigate("https://bun.sh");
await view.click("a[href='/docs']");
const title = await view.evaluate("document.title");
console.log(title);
await Bun.write("page.png", await view.screenshot());
macOS 默认使用系统 WebKit。
macOS、Linux 和 Windows 也可以驱动已经安装的 Chrome、Chromium 或 Edge。
因此在 Windows 和 Linux 上使用 Chrome 路径时,机器上需要先安装对应浏览器。
它适合做网页截图、后台巡检、自动化登录后的页面操作和简单的端到端脚本。
Bun.markdown:Markdown 解析器直接可用
版本:1.3.8 提供,1.4 增强
Bun.markdown 可以把 Markdown 转成 HTML、React 元素或终端 ANSI 文本。
最简单的用法如下。
ts
const markdown = "# Hello **world**";
const html = Bun.markdown.html(markdown);
console.log(html);
// <h1>Hello <strong>world</strong></h1>
在 React 页面中,可以直接返回 React 元素。
tsx
export default function ReadmePage({ readme }: { readme: string }) {
return Bun.markdown.react(readme);
}
它支持 GFM 表格、删除线、任务列表和自动链接。
它还支持自定义渲染器,例如把标题变成终端的粗体和下划线。
ts
const output = Bun.markdown.render("# Hello\n\n**bold**", {
heading: (children) => `\x1b[1;4m${children}\x1b[0m\n`,
paragraph: (children) => `${children}\n`,
strong: (children) => `\x1b[1m${children}\x1b[22m`
});
process.stdout.write(output);
有一个安全边界必须记住。
Bun.markdown.html() 不会自动清洗 HTML。
原始 HTML、事件属性和 javascript: 链接可能原样输出。
所以渲染不可信用户内容时,要先使用 HTML sanitizer,再把结果放进网页。
Bun.cron:把定时任务交给操作系统
版本:1.3.11 提供,1.4 增强
Bun.cron 可以注册操作系统级定时任务。
Linux 使用 crontab,macOS 使用 launchd,Windows 使用 Task Scheduler。
ts
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");
worker.ts 可以导出 scheduled 处理器。
ts
export default {
async scheduled(controller: { cron: string; scheduledTime: number }) {
console.log(controller.cron, controller.scheduledTime);
await generateWeeklyReport();
}
};
async function generateWeeklyReport() {
console.log("report generated");
}
如果只想在当前 Bun 进程中运行,也可以传函数。
ts
using job = Bun.cron("*/5 * * * *", async () => {
await cleanupTempFiles();
});
job.unref();
文件形式依赖操作系统的任务调度权限。
函数形式只运行在当前事件循环里,不会注册系统任务。
Bun 1.4 默认使用本地时间,并增加了 { tz } 选项指定时区。
跨地区部署时,建议明确写出时区,不要依赖服务器默认时区。
Bun.Terminal:内置伪终端
版本:1.3.5 提供,1.4 增强
Bun.Terminal 是内置的 PTY,也就是可以运行交互式命令的伪终端。
它可以驱动 bash、vim 或 htop,不需要额外安装 node-pty。
ts
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(_terminal, data) {
process.stdout.write(data);
}
}
});
proc.terminal.write("echo Hello from PTY!\n");
Linux、macOS 和 Windows 都支持这个 API。
示例使用 bash,Windows 上需要换成机器上实际存在的 Shell。
它适合做终端面板、远程命令执行界面和交互式 CLI 包装器。
内置解析器:JSON5、JSONL、JSONC、XML 和 TOML
版本:1.3.x 起陆续内置,1.4 继续增强
配置文件和日志解析,是另一批高频安装的依赖。
Bun 把它们也做成了内置 API。
Bun.JSON5
Bun.JSON5 用来解析和生成 JSON5,也就是允许注释、尾逗号和不加引号的键名的那种 JSON。
ts
const config = Bun.JSON5.parse(`{
// 数据库配置
host: "localhost",
port: 3306,
}`);
console.log(config.port);
// 3306
.json5 文件也可以直接 import。
ts
import config from "./config.json5";
它替代的是 json5 这个包。
Bun.JSONL
Bun.JSONL 用来处理按行分隔的 JSON,也就是每一行一个 JSON 对象的格式。
日志文件和大规模数据导出经常用它。
ts
const events = Bun.JSONL.parse('{"type":"login"}\n{"type":"logout"}\n');
console.log(events.length);
// 2
处理流式数据时,可以用 parseChunk() 一块一块地解析,不用等整个文件读完。
它替代的是 ndjson。
Bun.JSONC
Bun.JSONC.parse() 解析带注释和尾逗号的 JSON,tsconfig.json 用的就是这种格式。
ts
const tsconfig = Bun.JSONC.parse(`{
"compilerOptions": {
"strict": true, // 严格模式
},
}`);
console.log(tsconfig.compilerOptions.strict);
// true
它替代的是 jsonc-parser。
Bun.XML
Bun.XML 是 SIMD 加速的 XML 解析器和序列化器。
.xml 文件可以直接 import。
ts
import sitemap from "./sitemap.xml";
注意 1.4 的一个行为变化,.xml 导入现在返回解析后的对象,不再是文件路径。
需要路径时,要显式改用 --loader .xml:file。
它替代的是 fast-xml-parser 和 xml2js。
Bun.TOML
Bun.TOML 支持 TOML v1.1.0,通过了 toml-test 全部 708 个用例。
1.4 新增了 stringify(),可以把对象序列化回 TOML。
ts
const toml = Bun.TOML.stringify({
name: "my-app",
scripts: { dev: "bun run dev.ts" }
});
console.log(toml);
它替代的是 @iarna/toml。
压缩流、textStream() 和 Bun.Archive
版本:1.3.x 起陆续内置,1.4 继续增强
CompressionStream 和 DecompressionStream
Web 标准的压缩流在 Bun 里可以直接用,支持 gzip、deflate、deflate-raw、brotli 和 zstd。
ts
const compressed = new Blob(["hello world".repeat(1000)]).stream().pipeThrough(new CompressionStream("gzip"));
await Bun.write("data.gz", compressed);
解压反过来用 DecompressionStream 就行。
它适合流式地压缩和解压数据,和 fetch() 的 compress 选项是两回事。
Response.textStream()
textStream() 返回一个按 UTF-8 解码好的字符串流,读取大文本响应时不用自己拼 TextDecoder。
ts
const response = await fetch("https://example.com/big.txt");
for await (const chunk of response.textStream()) {
process.stdout.write(chunk);
}
Bun.Archive
Bun.Archive 用来创建和解压 tar 包,整个过程在主线程之外执行,不会卡住事件循环。
构建产物打包、缓存归档这类场景,不再需要安装 tar 这个依赖。
具体 API 可以看官方文档的 Archive 一节。
bun run --parallel:并行跑多个脚本
版本:1.3.9 提供,1.4 增强
以前经常要安装 concurrently 或 npm-run-all 才能并行跑脚本。
Bun 1.4 可以直接这样写。
bash
bun run --parallel build test
也可以使用脚本名通配符。
bash
bun run --parallel "build:*"
在 Monorepo 中,可以让所有 workspace 同时执行 build。
bash
bun run --parallel --filter '*' build
某个 workspace 失败后仍然继续执行其他任务。
bash
bun run --parallel --no-exit-on-error --filter '*' test
输出会自动带上脚本名,多个任务同时输出时更容易定位来源。
需要注意,脚本如果共用端口、临时目录或数据库,仍然要自己处理资源冲突。
Bun.serve():直接服务静态目录
版本:1.4 新增
Bun.serve() 的路由现在可以直接映射一个目录。
ts
Bun.serve({
port: 3000,
routes: {
"/static/*": { dir: "./public" }
}
});
public/index.html 会作为目录首页返回。
Bun 还会自动处理 Content-Type、ETag、Last-Modified、304 和 Range。
这意味着很多项目不再需要单独安装 express.static、serve-static 或 sirv。
视频和大文件下载也可以直接利用断点范围请求。
ts
Bun.serve({
port: 3000,
routes: {
"/video.mp4": new Response(Bun.file("./video.mp4"))
}
});
测试范围请求。
bash
curl -H "Range: bytes=0-1023" http://localhost:3000/video.mp4
正常情况下会返回 206 Partial Content。
Bun 会规范化静态文件路径,Linux 上还会使用 openat2 限制符号链接越出目标目录。
顺带一提流式响应的背压处理(1.4 新增)。
当客户端接收很慢、socket 发送缓冲区被填满时,Bun 会暂停流的 pull(),等缓冲区排空再继续。
Bun 1.3 的行为是把发不出去的数据一直堆在内存里,慢客户端足够久之后,进程会被拖垮。
1.4 之后,大文件和流式接口服务慢客户端时稳定了很多。
fetch() 请求压缩:上传大 JSON 更省流量
版本:1.4 新增
fetch() 新增了 compress 选项,可以在发送请求前压缩请求体。
支持 gzip、deflate、br 和 zstd。
ts
const response = await fetch("https://api.example.com/upload", {
method: "POST",
body: JSON.stringify({ items: Array(10_000).fill({ ok: true }) }),
compress: { encoding: "gzip", level: 6 }
});
console.log(response.status);
Bun 会自动设置 Content-Encoding,并让 Content-Length 反映压缩后的大小。
字符串、ArrayBuffer、TypedArray 和 Blob 等缓冲体会自动压缩。
流式请求体会原样发送,不会自动压缩。
服务端必须支持对应的 Content-Encoding。
fetch() 的代理选项也增强了(1.3.4 提供)。
proxy 现在可以传对象,直接给代理服务器发自定义请求头,比如 Proxy-Authorization。
ts
await fetch("https://api.example.com/data", {
proxy: {
url: "http://proxy.example.com:8080",
headers: { "Proxy-Authorization": "Bearer token" }
}
});
公司内网走认证代理的场景,以前要自己拼头,现在一个对象就够了。
TLS 会话复用也是 1.4 的新变化。
对同一个来源的第二次冷连接,可以复用缓存的会话,1 个往返就能恢复连接,不用再走完整的握手和证书链校验。
高频短连接的服务,比如定时调外部 API 的任务,连接开销会小不少。
HTTP/2 和 HTTP/3:有趣,但先别急着上生产
版本:1.3.14 提供,1.4 增强
fetch() 可以显式指定 HTTP/2 或 HTTP/3。
ts
const response = await fetch("https://example.com", {
protocol: "http3"
});
console.log(response.status);
HTTP/2 下,同一个来源的并发请求可以复用一条连接。
Bun.serve() 也可以开启 HTTP/3。
ts
Bun.serve({
port: 443,
tls: {
cert: Bun.file("./cert.pem"),
key: Bun.file("./key.pem")
},
http3: true,
fetch() {
return new Response("hello");
}
});
HTTP/3 会额外监听同端口的 UDP。
官方明确把这两个能力标成实验性功能。
Bun.serve() 的 HTTP/3 还不支持完整的 0-RTT 恢复,HTTP/3 上的 server.upgrade() 也会返回 false。
因此可以用于实验和压测,不建议现在直接作为生产默认配置。
bun:ffi:更快的原生函数调用
版本:1.4 新增引擎级实现
bun:ffi 使用 JavaScriptCore 内置的 FFI 能力调用 C 函数。
官方基准中,部分空调用约快 3 倍,CString 场景约快 3.8 倍。
下面是 API 形状示例。
ts
import { dlopen } from "bun:ffi";
const { symbols } = dlopen("libhash.so", {
hash: {
args: ["buffer", "buffer_length"],
returns: "cstring"
}
});
const data = new Uint8Array([1, 2, 3]);
const digest = symbols.hash(data, data);
console.log(digest);
libhash.so 和 hash 只是示例占位符。
真实运行时需要准备和当前操作系统、架构及 ABI 匹配的动态库。
Bun 1.4 中,returns: "cstring" 返回普通字符串,C 的 NULL 指针返回 null。
如果使用 CString 管理内存,需要保留原始指针,因为 CString 不再提供 .ptr。
全局虚拟存储:重复安装依赖少复制几次
版本:1.3.14 提供,1.4 收录并继续优化
选用 isolated linker 后,全局虚拟存储才会启用。
依赖包只提取一次到 Bun 的全局缓存,再通过符号链接放进每个项目的 node_modules/.bun/ 目录。
这是需要主动开启的选择,现有项目升级 Bun 后不会自动切换。
bash
bun install --linker=isolated
也可以在 bunfig.toml 中固定配置。
toml
[install]
linker = "isolated"
它适合本机有很多项目,或者 CI 缓存比较稳定的场景。
官方文章称,锁文件存在、缓存预热的 CI 场景中,1400 个包的安装最多可以快 7 倍。
新建 Monorepo 默认使用 isolated linker。
已有锁文件不会因为升级 Bun 就自动改变目录布局。
bun pm diff:升级依赖前先看代码差异
版本:1.4 新增
升级依赖之前,最怕的是不知道新版本到底改了什么。
bun pm diff 会对比两个包版本,并提示文件变化、安装脚本和危险 Node.js 模块导入。
bash
bun pm diff react
bun pm diff react@18.2.0 19.0.0
bun pm diff ./vendored-pkg pkg@2.1.0
还可以只看匹配的文件。
bash
bun pm diff react-dom@18.2.0 18.3.1 "*.min.js"
它适合作为升级依赖前的人工检查步骤。
bun audit fix:修复依赖漏洞
版本:1.4 新增
bash
bun audit fix
这个命令会尝试升级存在漏洞的依赖并重新安装。
如果需要跨越 major 版本,可以使用 --latest。
bash
bun audit fix --latest
bun audit fix --dry-run
--dry-run 只展示准备修改的内容,不立即写入依赖。
如果上游依赖的版本范围阻止升级,Bun 可能只能修复一部分漏洞。
所以命令成功执行不等于所有漏洞都已经解决,仍然要检查最终报告。
bun dedupe、bun prune 和许可证清单
版本:1.4 新增
bun dedupe 会合并满足相同 semver 范围的重复依赖。
bash
bun dedupe
bun dedupe --check
--check 适合放进 CI,发现可以去重的依赖时让检查失败。
bun prune 用来删除锁文件中不存在的依赖。
bash
bun prune
bun prune --production
典型容器流程如下。
dockerfile
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
RUN bun prune --production
这样构建阶段保留开发依赖,发布镜像只留下生产依赖。
查看生产依赖的许可证信息。
bash
bun pm licenses --prod --json > licenses.json
Workspace filter 和 catalog
版本:1.4 新增或增强
可以只给某个 workspace 添加依赖。
bash
bun add zod --filter api
也可以运行某个 workspace 以及它的依赖。
bash
bun run --filter 'web...' build
web... 表示 web 以及它依赖的 workspace。
...web 表示依赖 web 的 workspace。
catalog 用来统一管理 Monorepo 中的公共依赖版本。
bash
bun add react --catalog
workspace 的 package.json 可以引用 catalog 版本。
json
{
"dependencies": {
"react": "catalog:"
}
}
这样升级 React 时只需要维护一份版本号。
bun update 和嵌套 overrides
版本:1.4 新增
bun update 现在也更新传递依赖
以前 bun update 只更新 package.json 里直接声明的依赖。
1.4 之后,依赖的依赖也会一起更新。
bash
bun update
指定包名时,会更新这个包在整棵依赖树里的所有副本,包括其他 workspace 里的副本。
bash
bun update zod
bun update '@types/*' --latest
还可以按模式批量更新,比如一次更新所有类型包到最新版。
一个小变化要留意,更新的包当前不被任何地方依赖时,Bun 现在会报错退出,而不是把它当成新依赖加进来。
嵌套 overrides
以前想覆盖某个依赖的依赖,只能全局替换,影响整棵依赖树。
1.4 支持嵌套覆盖,只动指定父依赖下面的那一份。
json
{
"overrides": {
"express": {
"qs": "6.13.0"
},
"lodash@<4.17.21": "4.17.21"
}
}
这段配置的意思是,只把 express 内部的 qs 固定到 6.13.0,其他包用的 qs 不受影响。
lodash@<4.17.21 表示只覆盖低于这个版本的 lodash。
npm 的嵌套写法、Yarn 的 a/b 写法和 pnpm 的 a>b 写法都支持。
要注意,使用嵌套或版本限定的 override 后,锁文件会变成 lockfileVersion: 3,旧版 Bun 读不了。
测试能力:并行、隔离、分片和只跑受影响测试
bun test --parallel
版本:1.3.13 提供,1.4 增强
bash
bun test --parallel
bun test --parallel=4 --isolate
Bun 会启动多个 worker,把测试文件分配给空闲 worker。
覆盖率和 JUnit 报告会合并。
并行测试时要注意端口、数据库和临时目录的资源争用。
bun test --isolate
版本:1.3.13 提供,1.4 增强
--isolate 让每个测试文件跑在一个全新的 JavaScript global 里,但仍然在同一个进程内。
这和 Jest、Vitest 的默认行为一致。
专门解决一类经典问题,单个文件跑能过,整个套件一起跑就挂。
bash
bun test --isolate
两个文件之间,Bun 会做这些清理。
- 新建
globalThis,上一个文件挂上去的属性和补丁全部消失。 - 清空 ESM 和 CommonJS 模块注册表,每个文件重新执行自己的 import。
- 关闭上个文件留下的服务器、socket、文件监听器和子进程,取消定时器。
- 重新执行
--preload脚本。
转译产物和字节码在进程级缓存,第二个文件导入同一个模块时,只需要重新执行模块顶层代码,不用重新读文件和转译。
--parallel 默认隐含 --isolate,不想隔离时可以用 --no-isolate 关掉。
bun test --shard
版本:1.3.13 提供,1.4 增强
CI 有多个 runner 时,可以把测试切成多个分片。
bash
bun test --shard=1/3
bun test --shard=2/3
bun test --shard=3/3
Bun 会先排序测试文件,再按轮询方式分配到各个分片。
它还可以和 --parallel 组合,让每台 CI 机器继续使用多个 worker。
--timings
版本:1.4 新增
先记录每个测试文件的耗时。
bash
bun test --timings=timings.json --update-timings
下次分片时,Bun 会尽量按耗时而不是文件数量均衡。
bash
bun test --shard=1/3 --timings=timings.json
首次运行没有历史耗时,后续运行才会越来越准确。
--changed
版本:1.3.13 提供,1.4 增强
bash
bun test --changed
bun test --changed=main
Bun 会查看 Git 改动,再沿着 import 关系反向找到受影响的测试。
这比只运行改动文件对应的测试更实用。
动态导入或工具无法分析的依赖关系,可能不会被完整捕捉。
retry 和 repeats
版本:1.3.3 提供,1.4 增强
对偶发失败的测试,可以设置有限重试次数。
ts
test(
"unstable service",
async () => {
expect(await checkService()).toBe(true);
},
{ retry: 5 }
);
想验证测试稳定性,可以强制重复执行。
ts
test(
"stress check",
() => {
expect(runCheck()).toBe(true);
},
{ repeats: 20 }
);
也可以给整个测试套件设一个默认重试次数。
bash
bun test --retry 5
重试只是降低偶发失败对 CI 的影响,不是修复 flaky 测试的替代品。
构建能力:React Compiler 和构建期特性开关
内置 React Compiler
版本:1.4 新增
Bun 可以在构建时自动优化 React 组件和 Hooks。
bash
bun build ./src/index.tsx --outdir ./dist --react-compiler
API 方式如下。
ts
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
reactCompiler: true
});
官方基准中,大型 React 项目开启 Compiler 后只增加约 71 毫秒构建时间。
这个结果是官方测试项目的结果,不应当当成所有项目的固定耗时。
Barrel import 优化
版本:1.3.10 提供,1.4 增强
导入 antd 这类 barrel 包时,Bun 会尝试跳过没有被使用的模块。
ts
import { Button } from "antd";
如果包正确声明了 sideEffects: false,优化可以自动启用。
也可以显式配置。
ts
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
optimizeImports: ["antd", "@mui/material"]
});
只有在确认包没有依赖导入副作用时,才应该设置 sideEffects: false。
bun:bundle:构建期特性开关
版本:1.3.5 提供,1.4 继续完善
bun:bundle 可以在构建时决定某段代码是否存在。
ts
import { feature } from "bun:bundle";
if (feature("SUPER_SECRET")) {
console.log("secret feature enabled");
}
构建时传入特性名。
bash
bun build --feature=SUPER_SECRET ./src/index.ts
没有启用的分支会被当成死代码移除。
它是构建时开关,不是运行时环境变量。
内存文件、单文件 HTML 和资源嵌入
Bun.build() 使用内存文件
版本:1.3.6 提供,1.4 继续完善
代码生成器或测试工具不一定要先把文件写到磁盘。
ts
const result = await Bun.build({
entrypoints: ["/app/index.ts"],
files: {
"/app/index.ts": `import { greet } from "./greet.ts";
console.log(greet("World"));`,
"/app/greet.ts": `export function greet(name: string) {
return "Hello, " + name + "!";
}`
}
});
console.log(result.success);
files 的值可以是字符串、Blob 或 TypedArray。
单文件 HTML
版本:1.3.10 提供,1.4 增强
bash
bun build ./index.html --compile --target=browser --outdir=dist
生成的 HTML 会把脚本、样式和资源都内联。
它可以不依赖 Web 服务器,直接用 file:// 打开。
这适合离线演示、一次性工具和交付给客户的单页文件。
构建分析报告
版本:1.3.6 提供,1.4 增强
bash
bun build ./src/index.ts --outdir ./dist --metafile-md=./dist/meta.md
也可以同时输出 JSON 格式的 metafile。
bash
bun build ./src/index.ts \
--outdir ./dist \
--metafile=./dist/meta.json \
--metafile-md=./dist/meta.md
Markdown 报告会列出最大输入、入口点、依赖链和体积信息。
排查打包体积时,可以先看这份报告再决定是否拆包。
--asset:把静态资源嵌入可执行文件
版本:1.4 新增
bash
bun build ./build/index.js \
--compile \
--asset ./build/client \
--asset ./build/prerendered \
--outfile server
资源会被嵌入编译后的可执行文件,并通过 /$bunfs/ 目录树访问。
这个能力适合把前端静态文件和后端服务打成一个可分发文件。
它需要配合 --compile,嵌入资源不是外部可写目录。
ESM 字节码编译
版本:1.3.9 提供,1.4 增强
--bytecode 现在支持 ES modules。
bash
bun build ./app.ts --compile --bytecode --format=esm
--bytecode --format=esm 必须配合 --compile 使用。
开启后,top-level await、import.meta、动态 import 和代码分割都可以进入字节码编译的二进制。
以前 --bytecode 会强制输出 CommonJS,现在不用妥协了。
代码分割大幅提速
版本:1.4 新增
代码分割的可达性遍历改成了广度优先,复杂度变成 O(V+E)。
官方基准里,2 万个模块的菱形依赖图,链接时间从 4.65 秒降到 320 毫秒。
几千个模块的线性 import 链也不再撑爆调用栈。
这是构建器内部的改进,不需要改任何配置,升级就生效。
开发诊断工具
版本:1.4 新增或增强
生成 CPU profile。
bash
bun --cpu-prof app.ts
bun --cpu-prof-md app.ts
生成堆快照。
bash
bun --heap-prof app.ts
bun --heap-prof-md app.ts
cpuprofile 可以用 Chrome DevTools 打开。
heapsnapshot 可以用来排查内存增长和大对象。
父进程退出时,自动终止孤儿子进程。
bash
bun --no-orphans app.ts
CI 或生产启动脚本中,可以禁止自动加载 .env。
bash
bun --no-env-file app.ts
生产运行时和随手小工具
process.on("memoryPressure")
版本:1.3.x 起内置,1.4 继续完善
当操作系统内存吃紧时,它会通知 Bun,Bun 再在 process 上发出 memoryPressure 事件。
macOS、Linux 和 Windows 都支持。
可以利用它在系统杀掉进程之前主动释放内存。
ts
process.on("memoryPressure", () => {
cache.clear();
pool.drainIdle();
});
常见的动作是清空缓存、关闭空闲连接、停掉暂时不用的 worker。
要说明的是,这是操作系统的低内存通知,不是内存配额保证,释放动作得靠业务代码自己实现。
bun repl 和 bun ./README.md
bun repl 是原生 REPL,带语法高亮、历史记录和 Tab 补全,也支持 -e 和 -p 直接执行表达式。
bash
bun repl
bun repl -p '"1+1: " + (1 + 1)'
还有一个有意思的小功能,直接运行 Markdown 文件。
bash
bun ./README.md
Bun 会把 Markdown 渲染到终端,全程不启动虚拟机,看项目文档不用装 glow。
其他一句话更新
URLPattern这个 Web API 内置了,408 个 Web 平台测试通过,替代path-to-regexp。Bun.sliceAnsi()、Bun.wrapAnsi()和Bun.stringWidth()处理终端对齐,中文、emoji 和 ANSI 颜色码都算得准。Bun.spawn({ cgroup })可以在 Linux 上把子进程放进 cgroup 再启动,做资源限制。
Rust 重写和性能变化
Bun 1.4 是 Bun 改用 Rust 重写后的首个版本。
官方文章称,Claude Code 已经使用 Rust 版本运行了数月,Prisma Compute 也已经基于它发布。
性能方面,官方报告了这些结果。
- Claude Code 的 CPU p99 从 24% 降到 10%。
- Claude Code 的 CPU p50 从 5.8% 降到 2.5%。
- 小型 hello-world 服务的空闲 CPU 占用下降约 5 倍。
- HTTP 服务内存占用下降约 13% 到 48%。
- Windows 启动时间从 39.0 毫秒降到 15.5 毫秒。
- Linux 启动时间从 10.9 毫秒降到 5.1 毫秒。
- Linux 峰值内存从 33 MB 降到 14.6 MB。
new URL()最多快 4.6 倍。Buffer.from(str, "hex")约快 8 倍。base64url解码最高约快 46 倍。- Promise 操作约快 1.5 到 2.4 倍。
这些都是文章中的基准结果。
实际收益会受到代码类型、数据规模、操作系统和硬件影响。
Node.js 兼容性继续提高
Bun 1.4 对 Node.js 26.3.0 测试套件新增了 1,517 个通过测试。
这说明兼容范围在扩大,但不代表 Bun 已经百分之百兼容 Node.js。
官方还特别提到 Playwright、Next.js 16、Vitest、OpenTelemetry 和 dd-trace 的兼容改进。
原生 addon 需要关注新的 ABI 版本。
ts
console.log(process.versions.modules);
// 147
如果项目中有按 NODE_MODULE_VERSION 下载预编译 native addon 的依赖,需要确认它提供了 ABI 147 的构建产物。
安全增强
Bun 1.4 的安全变化不是锦上添花,而是升级理由之一。
fetch() 的证书校验回调有了更可靠的执行时机(1.4 新增)。
当你显式传入 tls: { checkServerIdentity } 回调时,它会在 TLS 握手完成后、请求写出前执行,重定向的每一跳也会再执行一次。
回调返回 Error 时,fetch() 直接拒绝,请求一个字节都不会发出去。
这个时机特别适合证书锁定,也就是只信任特定指纹的证书。
ts
const PINNED = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
await fetch("https://api.example.com/upload", {
method: "POST",
body: secretPayload,
tls: {
checkServerIdentity(_hostname, cert) {
if (cert.fingerprint256 !== PINNED) {
return new Error("pin mismatch");
}
}
}
});
其他几项安全收紧分布在 1.3.x 和 1.4 之间。
Bun.connect() 和 Bun.listen() 在开启 requestCert 时,默认执行 rejectUnauthorized(1.4 新增)。
tls.connect() 默认用 host 作为证书校验的服务器名(1.3.13)。
Redis 的 rediss:// 连接默认校验主机名(1.3.14)。
Bun.serve() 会拒绝畸形 HTTP framing(1.3.4)。
tar 包中试图越出解压目录的路径会被跳过(1.3.6)。
官方还提醒,少数在 1.3 上能连上的连接,升级后会变成证书校验错误,这是预期的收紧行为。
使用自签名证书或私有 CA 时,要显式提供 CA。
ts
await fetch("https://internal.example.com", {
tls: {
ca: [Bun.file("./internal-ca.pem")]
}
});
只有在明确知道风险时,才应该关闭证书验证。
ts
await fetch("https://localhost:8443", {
tls: {
rejectUnauthorized: false
}
});
生产环境不要把 rejectUnauthorized: false 当成常规修复方案。
平台支持
Bun 1.4 增加了 Windows ARM64 原生构建。
Surface、Snapdragon X 和 Ampere 等 ARM64 Windows 设备可以直接运行原生 Bun。
FreeBSD 14.3 及以上提供 x86_64 和 aarch64 原生构建。
Android aarch64 和 x64 仍然是实验性支持。
Linux 的最低 glibc 版本降到 2.17,最低内核版本为 3.10。
Bun 还开始为 TypeScript 7 做准备。
Windows 上的高精度定时器也得到改善,1 毫秒定时器现在大约 1.4 毫秒触发,而不是过去的 15.5 毫秒左右。
升级时最容易踩到的变化
writeHeader() 改成 writeHead()
旧代码。
ts
res.writeHeader(200, { "Content-Type": "text/plain" });
新代码。
ts
res.writeHead(200, { "Content-Type": "text/plain" });
以 node 身份运行时不再自动加载 .env
这个变化只影响 Bun 以 node 身份运行的场景,包括 bun --bun、bunx --bun 和把 Bun 软链接成 node 这三种情况。
这些方式现在和 Node.js 一样,不再自动读取 .env 文件。
如果脚本依赖 .env,要显式传入文件。
bash
node --env-file=.env ./check.js
直接用 bun ./check.js 运行脚本时,.env 仍然会自动加载,行为没有变化。
新 Monorepo 默认使用 isolated linker
如果必须保持旧的 hoisted 布局,可以显式写入配置。
toml
[install]
linker = "hoisted"
YAML 解析遵循 YAML 1.2
yes、no、on 和 off 会被解析成字符串,而不是布尔值。
GitHub Actions 中的 on: 也会按字符串处理。
TOML 和 bunfig.toml 更严格
未加引号的非法字符串、键值之间缺少换行,以及超过 Number.MAX_SAFE_INTEGER 的整数都会抛出 SyntaxError。
bun.lock 版本升级
Bun 1.4 的新锁文件是 lockfileVersion: 2。
GitHub 和 tarball 依赖会记录 SHA-512 完整性信息,Git 依赖会检查路径穿越。
旧版 v0 和 v1 锁文件仍然可以读取,执行一次 bun install 就会迁移。
使用嵌套 override 或按版本限定的 override 时,锁文件会升级到 lockfileVersion: 3,旧版 Bun 无法读取。
编译后的可执行文件不再乱读运行目录配置
bun build --compile 生成的可执行文件不再自动读取运行目录中的 tsconfig.json 和 package.json。
.env 和 bunfig.toml 仍然默认自动加载,不受这个变化影响。
如果确实需要旧行为,可以显式开启。
bash
bun build --compile \
--compile-autoload-tsconfig \
--compile-autoload-package-json \
./src/index.ts
其他值得搜索的迁移点
- 暂停模式下的
readable.read()不带 size 时每次只返回一个 chunk,需要循环读到null。 fs.rmdir(path, { recursive: true })改用fs.rm(path, { recursive: true, force: true })。Request或Response的clone()要放在读取 body 之前。fetch()网络错误现在是TypeError。Bun.cron()默认使用本地时间,旧代码依赖 UTC 时要显式写{ tz: "UTC" }。Bun.$的插值不再自动展开 glob,需要改用明确的 glob API 或命令参数。- MariaDB JSON 字段会直接解析成对象,不要再重复调用
JSON.parse()。
我怎么看
Bun 1.4 的重点不是单纯跑得更快,而是减少一个 JavaScript 项目需要安装和维护的外围工具。
图片处理、Markdown、定时任务、PTY、浏览器自动化、静态文件服务和配置解析,都开始有了内置方案。
对新项目来说,这会让依赖树更短,部署步骤更少。
对存量项目来说,真正需要认真评估的是兼容性和升级行为,而不是盲目替换所有依赖。
不知道你怎么看呢?欢迎在评论区留言。
感谢阅读,我是农村程序员,独立开发者,行业观察员,前端之虎陈随易。