作者 Jarred Sumner · 2025年9月28日
升级 Bun
sh
bun upgrade
bun install
中的 pnpm-lock.yaml
支持
bun install
现在会自动将 pnpm-lock.yaml
和 pnpm-workspace.yaml
文件迁移为 bun.lock
,并保留解析后的依赖版本。这包括对 pnpm 工作区和 catalog:
依赖的支持。
只需一条命令即可从 pnpm install
切换到 bun install
:
sh
# 在 pnpm 项目中
bun install
bun install
旨在与现有的 Node.js 项目协同工作。这意味着您可以在继续使用 Node.js 作为运行时的同时,利用 bun install
惊人的性能。
如果您的项目使用 pnpm-workspace.yaml
,您的 package.json 将被更新以包含 "workspaces": ["<每个包的名称>"]
。
使用 --cpu
和 --os
标志过滤可选依赖
您现在可以使用 bun install
中新的 --cpu
和 --os
标志来控制安装哪些特定于平台的可选依赖。当您为不同的目标环境(例如 Docker 容器或 CI/CD 流水线)安装依赖时,这非常有用。
您可以提供多个值来同时为多个目标安装依赖。
sh
# 为 Linux ARM64 目标安装可选依赖
bun install --os linux --cpu arm64
# 为 x64 架构的 macOS 和 Linux 安装
bun install --os darwin --os linux --cpu x64
# 为所有支持的平台安装
bun install --os '*' --cpu '*'
Bun 的 Redis 客户端现在支持发布/订阅
Bun 内置的 RedisClient
现在支持发布/订阅消息模式。您可以使用新的 .subscribe()
方法来监听特定频道上的消息,并使用 .publish()
方法来发送消息。
这使您可以直接在 Bun 应用程序中实现实时、事件驱动的通信模式。
subscriber.ts
ts
import { RedisClient } from "bun";
const subscriber = new RedisClient("redis://localhost:6379");
await subscriber.connect();
await subscriber.subscribe("my-channel", (message, channel) => {
console.log(`收到消息: "${message}" 来自频道: "${channel}"`);
// 收到消息: "Hello from Bun!" 来自频道: "my-channel"
});
publisher.ts
ts
import { RedisClient } from "bun";
const publisher = new RedisClient("redis://localhost:6379");
await publisher.connect();
// 短暂延迟以确保订阅者已准备就绪
setTimeout(() => {
publisher.publish("my-channel", "Hello from Bun!");
}, 100);
并发 bun test
bun test
现在支持使用 test.concurrent
在同一文件内并发运行多个 async
测试。这可以显著加速受 I/O 限制的测试套件,例如那些发起网络请求或与数据库交互的测试。
concurrent.test.ts
ts
import { test, expect } from "bun:test";
// 这三个测试将并行运行。
// 总执行时间将约为 1 秒,而不是 3 秒。
test.concurrent("向服务器 1 发送请求", async () => {
const response = await fetch("https://example.com/server-1");
expect(response.status).toBe(200);
});
test.concurrent("向服务器 2 发送请求", async () => {
const response = await fetch("https://example.com/server-2");
expect(response.status).toBe(200);
});
// 可以与 .each、.only 或其他修饰符链式调用:
test.concurrent.each([
"https://example.com/server-4",
"https://example.com/server-5",
"https://example.com/server-6",
])("向服务器 %s 发送请求", async (url) => {
const response = await fetch(url);
expect(response.status).toBe(200);
});
使用 describe.concurrent
并发运行测试组。
describe.concurrent.test.ts
ts
import { describe, test, expect } from "bun:test";
describe.concurrent("服务器测试", () => {
test("向服务器 1 发送请求", async () => {
const response = await fetch("https://example.com/server-1");
expect(response.status).toBe(200);
});
});
test("串行测试", () => {
expect(1 + 1).toBe(2);
});
默认情况下,最多会同时运行 20 个测试。您可以使用 --max-concurrency
标志来更改此设置。
concurrentTestGlob
使特定文件并发运行
要使特定文件并发运行,您可以在 bunfig.toml
中使用 concurrentTestGlob
选项。
bunfig.toml
toml
[test]
concurrentTestGlob = "**/integration/**/*.test.ts"
# 您也可以提供一个模式数组。
# concurrentTestGlob = [
# "**/integration/**/*.test.ts",
# "**/*-concurrent.test.ts",
# ]
当使用 concurrentTestGlob
时,所有匹配 glob 的文件中的测试都将并发运行。
test.serial
使特定测试顺序执行
当您使用 describe.concurrent
、--concurrent
或 concurrentTestGlob
时,您可能仍然希望某些测试顺序执行。您可以使用新的 test.serial
修饰符来实现这一点。
test.serial.test.ts
ts
import { test, expect } from "bun:test";
describe.concurrent("并发测试", () => {
test("异步测试", async () => {
await fetch("https://example.com/server-1");
expect(1 + 1).toBe(2);
});
test("异步测试 #2", async () => {
await fetch("https://example.com/server-2");
expect(1 + 1).toBe(2);
});
test.serial("串行测试", () => {
expect(1 + 1).toBe(2);
});
});
使用 --randomize
随机顺序运行测试
并发测试有时会暴露出测试对执行顺序或共享状态的意外依赖。您可以使用 --randomize
标志以随机顺序运行测试,以便更容易发现这些依赖关系。
当您使用 --randomize
时,Bun 将输出该次运行的种子。为了在调试时重现完全相同的测试顺序,您可以使用 --seed
标志并附上打印的值。使用 --seed
会自动启用随机化。
sh
# 以随机顺序运行测试
bun test --randomize
# 种子会在测试摘要中打印
# ... 测试输出 ...
# --seed=12345
# 2 通过
# 8 失败
# 在 2 个文件中运行了 10 个测试。 [50.00ms]
# 使用种子重现相同的运行顺序
bun test --seed 12345
bun test
现在支持链式修饰符
您现在可以在 test
和 describe
上链式调用修饰符,如 .failing
、.skip
、.only
和 .each
。以前,这会导致错误。
ts
import { test, expect } from "bun:test";
// 这个测试预期会失败,并且它为数组中的每个项目运行。
test.failing.each([1, 2, 3])("每个 %i", (i) => {
if (i > 0) {
throw new Error("此测试预期失败。");
}
});
CI 环境中更严格的 bun test
为防止意外提交,bun test
现在在 CI 环境中(即 CI=true
时)会在两种新情况下抛出错误:
- 如果测试文件包含
test.only()
。 - 如果快照测试(
.toMatchSnapshot()
或.toMatchInlineSnapshot()
)尝试在没有--update-snapshots
标志的情况下创建新快照。
这有助于防止临时聚焦的测试或无意的快照更改被合并。要禁用此行为,您可以设置环境变量 CI=false
。
测试执行顺序改进
测试运行器的执行逻辑已被重写,以提高可靠性和可预测性。这解决了大量关于 describe
块和钩子(beforeAll
、afterAll
等)执行顺序略有意外的问题。新的行为与 Vitest 等测试运行器更加一致。
并发测试限制
- 当使用
test.concurrent
或describe.concurrent
时,不支持expect.assertions()
和expect.hasAssertions()
。 - 不支持
toMatchSnapshot()
,但支持toMatchInlineSnapshot()
。 beforeAll
和afterAll
钩子不会并发执行。
bun feedback
在下一个 Bun 版本中
bun feedback <文件或文本> 让向 Bun 团队发送关于 Bun 的反馈变得更加容易 pic.twitter.com/CkJAKwllzU
--- Jarred Sumner (@jarredsumner) 2025年9月16日
Node.js 兼容性改进
node:http
http.createServer
现在支持 CONNECT
方法。这允许使用 Bun 创建 HTTP 代理。
node:dns
dns.resolve
的回调现在与 Node.js 匹配,不再传递额外的 hostname
参数。dns.promises.resolve
现在对于 A/AAAA 记录正确返回字符串数组而不是对象。
node:worker_threads
修复了一个在使用 port.on('message', ...)
或 port.addEventListener('message', ...)
时,MessagePort
通信在转移到 Worker
后会失败的 bug。这是由于 Web Workers 和 worker_threads
之间的差异导致的。
node:crypto
修复了在使用 JWK 格式的椭圆曲线密钥且 dsaEncoding: 'ieee-p1363'
时,crypto.createSign().sign()
可能发生的假设性崩溃。
node:http2
修复了关闭套接字时的内存泄漏问题。
node:net
修复了 net.connect()
中的句柄泄漏问题,该问题导致在建立大量连接时内存使用量增长。
node:tty
在 Windows 上,TTY 原始模式 (process.stdin.setRawMode(true)
) 现在能正确处理终端 VT 控制序列,提高了与 Node.js 的兼容性,并支持诸如带括号的粘贴模式等功能。
使用 --use-system-ca
来使用系统的受信任证书
Bun 现在可以配置为使用操作系统的受信任根证书来建立 TLS 连接,此外还可以使用内置的 Mozilla CA 存储。这在具有自定义证书颁发机构的企业环境中或用于信任本地安装的自签名证书时非常有用。
要启用此功能,可以使用新的 --use-system-ca
命令行标志,或者设置 NODE_USE_SYSTEM_CA=1
环境变量,这使得 Bun 与 Node.js 的行为保持一致。
sh
# 此命令将使用操作系统的受信任 CA 来验证
# "internal.service.corp" 的证书。
bun run --use-system-ca index.js
ts
// index.js
const response = await fetch("https://internal.service.corp");
console.log(response.status);
process.report.getReport()
现已在 Windows 上实现
与 Node.js 兼容的 API process.report.getReport()
现已在 Windows 上完全实现。以前,这会抛出"未实现"错误。此函数生成关于当前进程的全面诊断报告,包括系统信息、JavaScript 堆统计信息、堆栈跟踪和已加载的共享库。
此更改提高了依赖此 API 进行诊断或环境检测的工具的兼容性。
ts
// 在 Windows 上,现在这会返回一个详细的报告对象。
const report = process.report.getReport();
console.log(report.header.osVersion); // 例如:"Windows 11 Pro"
console.log(report.header.cpus.length > 0); // true
console.log(report.javascript.heap.heapSpaces.length > 0); // true
bun build --compile
中的 Windows 代码签名
bun build --compile
可以从 JavaScript 或 TypeScript 入口点创建独立的可执行文件。在 Windows 上,这些可执行文件通常基于预先签名的 bun.exe
二进制文件。以前,当 Bun 嵌入应用程序代码和资源时,它会使原始签名失效,从而阻止开发人员应用他们自己的代码签名证书。
此版本引入了自动的"Authenticode"签名剥离。当您创建可执行文件时,Bun 现在会移除原始签名,允许您使用标准的 Windows 工具(如 signtool.exe
)使用自己的证书对编译后的二进制文件进行签名。这对于向 Windows 用户分发受信任的应用程序至关重要。
sh
# 1. 将您的应用程序编译为独立可执行文件
bun build ./index.ts --compile --outfile my-app.exe
# 2. 使用您自己的证书对生成的可执行文件进行签名
signtool.exe sign /f MyCert.pfx /p MyPassword my-app.exe
Bun.build
新增 jsx
配置对象
Bun.build
中 JSX 转换的配置现在集中在一个新的 jsx
对象中。这包括先前通过 tsconfig.json
配置的选项,如 jsxFactory
、jsxFragment
和 jsxImportSource
。
ts
await Bun.build({
entrypoints: ["./index.jsx"],
outdir: "./dist",
jsx: {
runtime: "automatic", // "automatic" 或 "classic"
importSource: "preact", // 默认为 "react"
factory: "h", // 默认为 "React.createElement"
fragment: "Fragment", // 默认为 "React.Fragment"
development: false, // 使用 `jsx-dev` 运行时,默认为 `false`
sideEffects: false,
},
});
Bun.SQL 中的 sql.array
辅助函数
Bun.SQL 中的 sql.array
辅助函数使得处理 PostgreSQL 数组类型变得容易。您可以将数组插入到数组列中,并指定 PostgreSQL 数据类型以进行适当的类型转换。
ts
import { sql } from "bun";
// 插入一个文本值数组
await sql`
INSERT INTO users (name, roles)
VALUES (${"Alice"}, ${sql.array(["admin", "user"], "TEXT")})
`;
// 使用 sql 对象表示法更新数组值
await sql`
UPDATE users
SET ${sql({
name: "Bob",
roles: sql.array(["moderator", "user"], "TEXT"),
})}
WHERE id = ${userId}
`;
// 与 JSON/JSONB 数组一起使用
const jsonData = await sql`
SELECT ${sql.array([{ a: 1 }, { b: 2 }], "JSONB")} as data
`;
// 支持各种 PostgreSQL 类型
await sql`SELECT ${sql.array([1, 2, 3], "INTEGER")} as numbers`;
await sql`SELECT ${sql.array([true, false], "BOOLEAN")} as flags`;
await sql`SELECT ${sql.array([new Date()], "TIMESTAMP")} as dates`;
sql.array
辅助函数支持所有主要的 PostgreSQL 数组类型,包括 TEXT
、INTEGER
、BIGINT
、BOOLEAN
、JSON
、JSONB
、TIMESTAMP
、UUID
、INET
等等。
顶层 await 改进
Bun 的打包器现在对顶层 await 有了更好的支持。
当存在使用顶层 await 的循环依赖时,Bun 现在会将模块包装在 await Promise.all
中以确保它们全部加载。我们还修复了一些边缘情况,这些情况可能导致在某些情况下打包的模块中缺少 async
关键字。