前端5月29日 00:26
Bun 在 I/O 性能方面做了哪些优化?Bun用Zig语言编写,I/O优化的核心是**绕过libuv,直接走系统调用**。具体优化:**文件I/O**使用`Bun.file()`API,底层通过mmap内存映射和直接syscall读写,Linux上利用io_uring实现异步,避免Node.js经libuv线程池的开销;**HTTP服务**用`Bun.serve()`构建,基于零拷贝响应和原生HTTP解析器(不用Node的http-parser),基准吞吐量高出Node.js约3倍;**SQLite**内置驱动直接编译进运行时,省去FFI调用开销;**启动优化**通过原生TypeScript执行(无需tsc编译步骤)和全局缓存减少冷启动I/O。总结:Node.js的I/O路径是JS→V8→libuv→OS,Bun是JS→JavaScriptCore→Zig syscall→OS,少了中间层。
## 追问
- io_uring在Linux上的性能优势具体来自哪里?macOS上Bun用什么替代方案?
- Bun.serve的零拷贝是如何实现的?Response对象哪些场景下会触发拷贝?
- Bun.file()和Node.js的fs.promises.readFile在处理大文件时的内存占用差多少?
- 内置SQLite驱动和better-sqlite3相比,性能差距主要在哪?
- Bun的HTTP客户端(Bun.fetch)也做了类似的零拷贝优化吗?
## 写段代码
```javascript
// Bun: 零拷贝文件服务
const file = Bun.file('./data.json');
Bun.serve({
fetch: (req) => new Response(file), // 直接返回文件流,无需读取到内存
port: 3000
});
```标签
Bun
Bun 是一个新兴的 JavaScript 运行时环境,由 Jarred Sumner 开发,旨在提升 Node.js 的性能瓶颈。Bun 基于 Zig 语言编写,集成了运行时、包管理器(bun install)、构建工具和测试框架,形成一体化开发体验。它拥有极快的启动速度和执行效率,内置 TypeScript 支持,兼容大部分 Node.js API。Bun 的包管理器比 npm/yarn/pnpm 更快,依赖安装速度显著提升。其构建工具支持热重载、代码压缩和打包,简化前端开发流程。Bun 致力于降低资源消耗、提升开发效率,适用于高性能服务端和现代前端项目。随着生态不断完善,Bun 正逐步成为 JavaScript 开发的新选择。

前端5月28日 03:10
Bun 的启动速度和依赖安装速度为什么快?Bun 的启动速度和依赖安装速度之所以远超 Node.js 和 npm,核心原因在于它选择了完全不同的底层引擎和架构方案。下面从启动速度和依赖安装两个维度分别说明。
## 启动速度快的原因
Bun 启动速度远快于 Node.js,根本原因在于使用了 **JavaScriptCore(JSC)引擎**而非 V8,配合 **Zig 语言**编写的运行时实现。这和很多人印象中的"Bun 基于 V8"完全相反——Bun 从未使用 V8,它选择的是 WebKit/Safari 同源的 JSC 引擎。下面从引擎、语言、模块三个层面展开。
### JavaScriptCore 的分层编译策略
JSC 采用四级编译流水线:**LLInt → Baseline JIT → DFG JIT → FTL JIT**。其中 LLInt(Low Level Interpreter)是关键——它可以直接解释执行字节码,跳过编译阶段。这意味着 Bun 在启动时不需要等待 JIT 编译完成,LLInt 直接开始执行代码,冷启动开销极低。
对比来看,V8 的编译管线是 **Ignition(字节码解释器)→ Sparkplug(非优化编译)→ TurboFan(优化编译)**。V8 的设计哲学是**用启动时间换取峰值性能**:先花时间编译出优化代码,再在长期运行中获得高吞吐量。这对长期运行的服务端应用很合适,但对于启动-执行-退出的短生命周期场景(如 CLI 工具、Serverless 函数、构建脚本),JSC 的渐进式编译策略显然更有优势。
具体到执行流程的差异:V8 启动时需要先解析 JavaScript 源码生成 AST,再将 AST 编译为字节码,接着由 Sparkplug 编译为非优化的机器码才开始执行。JSC 的 LLInt 可以直接解释执行字节码,无需等待机器码生成,第一个函数的执行延迟更低。之后随着代码反复执行,JSC 会逐步将热点代码提升到更高级别的 JIT 编译——从 Baseline JIT 到 DFG JIT,再到 FTL JIT,这和 V8 的优化方向一致,但起点更轻量。
根据 Bun 官方基准测试数据,Bun 的冷启动时间约为 8-15ms,而 Node.js 需要 60-120ms,Deno 需要 40-60ms。在 Serverless 场景下,这种 4-10 倍的启动速度差距会直接转化为用户可感知的延迟差异。2026 年已有团队将微服务从 Node.js 迁移到 Bun 后,获得了 60% 的延迟降低和 20% 的基础设施成本节省。
```bash
# 本地冷启动对比
time bun run index.ts
# real 0m0.008s
time node index.js
# real 0m0.065s
time deno run index.ts
# real 0m0.042s
```
为什么 JSC 选择了渐进式编译而 V8 选择了激进式编译?这和两个引擎的历史定位有关:JSC 最初是为 Safari 浏览器设计的,网页中 JavaScript 执行频繁但每次执行时间短,快速启动比峰值性能更重要;V8 是为 Chrome 和 Node.js 设计的,Node.js 服务端进程通常运行数天甚至数周,峰值吞吐量才是核心指标。Bun 选择 JSC 正是因为它更适合 CLI 和脚本场景——大部分脚本执行几秒就结束了,根本等不到 TurboFan 的优化生效。Deno 虽然也使用 V8,但通过 V8 Snapshot 技术将序列化的堆快照直接加载到内存,跳过了部分初始化步骤来加速启动,不过仍不及 JSC 的原生优势。
### Zig 带来的原生性能优势
Bun 使用 Zig 语言编写(而不是 Rust),编译后生成单个静态链接的原生二进制文件。Zig 有几个对启动速度至关重要的特性:
- **comptime(编译期代码执行)**:Zig 允许在编译阶段执行代码,Bun 利用这个能力将大量初始化逻辑前移到编译期,运行时零开销。比如 Bun 内置的 Node.js 兼容 API 绑定就是在编译期生成的,不需要运行时动态注册和查找。这意味着当你 `import { readFileSync } from 'fs'` 时,对应的 Zig 实现已经在编译时链接好了,运行时只需要一次函数指针跳转。
- **不依赖 libc**:Zig 可以编译为不依赖系统 libc 的二进制文件,避免了动态链接库加载的开销,同时让交叉编译变得简单可靠。一个 `bun` 可执行文件就能在 macOS、Linux、Windows 上运行,无需额外依赖。对比 Node.js 的分发需要针对不同平台提供不同的二进制文件,且依赖系统级的动态库。
- **手动内存管理,零隐藏分配**:相比带 GC 的语言,Zig 没有运行时垃圾回收器的初始化和暂停开销,启动路径更短更可预测。这对 CLI 工具和脚本执行场景尤为重要——没有人希望自己的构建脚本因为 GC 停顿而变慢。
一个常见的误解是"Bun 用 Rust 写的所以快"。实际上 Bun 选择 Zig 而非 Rust,正是因为 Zig 的 comptime 能力和对底层控制的精确性更适合构建高性能运行时。Rust 的安全保证虽然有价值,但在运行时核心路径上,Zig 的显式控制更高效。Bun 的作者 Jarred Sumner 在多个技术演讲中解释过这个选择:Zig 的 comptime 让 Bun 可以在编译期生成大量胶水代码,而 Rust 的宏系统虽然强大但不如 comptime 灵活。另外 Zig 的交叉编译体验远优于 Rust——`zig build` 直接生成目标平台二进制,不需要配置复杂的 toolchain,这对 Bun 需要支持 macOS、Linux、Windows 三大平台的场景至关重要。
### 模块解析与加载优化
Bun 在模块系统上做了三层优化:
1. **内置 Node.js 兼容 polyfill**:`fs`、`path`、`http`、`buffer`、`crypto`、`net`、`os` 等常用模块直接内置于 Bun 运行时中,启动时无需从 `node_modules` 查找和加载,省去了文件系统 I/O。这些 polyfill 用 Zig 从零实现,比 JavaScript 实现的 polyfill 快得多。Node.js 的这些内置模块虽然是 C++ 编写,但需要通过 V8 的绑定层(N-API / NAN)桥接到 JavaScript,有额外的类型转换和异常处理开销;Bun 的 Zig 实现直接与 JSC 交互,调用路径更短。
2. **Transpiler 集成**:Bun 内置了用 Zig 编写的 JavaScript/TypeScript transpiler,TypeScript 和 JSX 的转换在进程内完成,不需要启动外部进程(如 `ts-node` 或 `esbuild`),避免了进程间通信的开销。这也是为什么 `bun run` 可以直接执行 `.ts` 文件而无需任何配置。内置 transpiler 的转换速度接近 `esbuild`,但省去了进程启动的额外开销。
3. **懒加载策略**:只在代码真正 `import` 时才解析和编译对应模块,而不是启动时全量加载整个模块图。对于包含数百个依赖的大型项目,这个优化能显著减少启动时的工作量——很多依赖可能根本不会被实际调用。
```typescript
// Bun 直接运行 TypeScript,无需额外配置
bun run server.ts
// 对比 Node.js 需要安装和配置 ts-node
npx ts-node server.ts // 启动 ts-node 进程 → 加载 tsconfig → 编译 → 执行
// 或者使用 --loader 标志(更慢,且实验性)
node --loader ts-node/esm server.ts
```
Bun 的 transpiler 虽然不如 `tsc` 严格(不做类型检查),但对于运行来说足够了。类型检查可以交给 IDE 和 CI 中的 `tsc --noEmit` 完成,运行时只需要语法转换。这种"编译时类型检查 + 运行时快速转换"的分工是 Bun 的设计理念之一,也是它比 `ts-node` 快一个数量级的原因——`ts-node` 每次运行都要完整编译整个项目,而 Bun 只转换当前需要执行的文件。
## 依赖安装速度快的原因
`bun install` 比 `npm install` 快约 **25 倍**,这来自包管理器架构层面的多个关键设计决策,而不仅仅是"用了更快的语言"。下面逐一拆解每个优化点。
### 全局缓存与硬链接机制
Bun 将所有下载过的包存储在全局缓存目录(`~/.bun/install/cache/`)中。安装依赖时,Bun 不会像 npm 那样把包复制到每个项目的 `node_modules`,而是:
- **Linux 上创建硬链接(hard link)**:多个项目的 `node_modules` 指向同一份磁盘数据,零拷贝开销。硬链接意味着不同项目引用的是同一份 inode,不占用额外磁盘空间。
- **macOS 上使用写时复制克隆(copy-on-write clone)**:利用 APFS 文件系统的 CoW 特性,同样实现零拷贝,且修改时不影响其他项目。
这意味着安装一个已缓存过的包几乎是瞬时完成的——只需要创建一个文件系统链接,不涉及网络请求、解压和文件写入。对比 npm 的流程:下载 tarball → 解压到临时目录 → 复制到 node_modules → 校验 integrity hash,Bun 直接跳过了前三步。
```bash
# 查看全局缓存
ls ~/.bun/install/cache/
# 首次安装后,后续项目的安装几乎零开销
# 硬链接验证:两个项目的 node_modules 指向同一份数据
stat -c '%i' project-a/node_modules/lodash/index.js
stat -c '%i' project-b/node_modules/lodash/index.js
# 两个 inode 号相同,证明是硬链接
```
这种缓存策略带来的好处不仅是速度。想象你有 10 个项目都依赖 `lodash@4.17.21`:npm 会在每个项目中复制一份,占用 10 份磁盘空间;Bun 只存储一份,10 个项目通过硬链接共享,磁盘占用几乎不增加。在 CI/CD 环境中,缓存命中率更高,构建时间也更稳定可预测。这在大型 monorepo 中效果尤其明显——几十个子项目共享同一份全局缓存,安装时间几乎不随项目数量增长。
### 智能增量安装
当项目中已经存在 `bun.lock` 且 `package.json` 未变更时,Bun 采用懒加载策略:只安装缺失的依赖。如果某个包已经存在于 `node_modules` 的预期位置,Bun 不会重新下载或校验,直接跳过。这使得二次安装几乎是瞬时完成。
这个策略和 npm 的行为有本质区别:npm 每次运行 `npm install` 都会重新校验 `node_modules` 中所有包的完整性(读取每个包的 `package.json`、比对版本号、验证 integrity hash),即使没有任何变更也要走一遍检查流程。在包含数百个依赖的大型项目中,这个校验过程可能耗时数秒到数十秒。Bun 的智能跳过策略将这个时间压缩到几乎为零。
### 并行执行生命周期脚本
npm 和 yarn 串行执行 `postinstall`、`preinstall` 等生命周期脚本,而 Bun 默认**并行运行**这些脚本,可以通过 `--concurrent-scripts` 参数调整最大并发数:
```bash
# 调整并行脚本数
bun install --concurrent-scripts=8
# 完全禁用并行(排查问题时有用)
bun install --concurrent-scripts=1
```
在包含大量带有 native 构建步骤的依赖时(如 `node-sass`、`bcrypt`、`sharp`、`esbuild`、`canvas`),并行执行生命周期脚本的提速效果非常显著。假设一个项目有 5 个需要编译的 native 模块,每个编译耗时 3 秒:npm 串行需要 15 秒,Bun 并行只需要约 3 秒。这也是为什么 Bun 在安装 native 依赖时优势特别明显。
### 高效的锁文件格式
Bun 使用二进制格式的 `bun.lock` 文件,相比 npm 的 JSON 格式 `package-lock.json`,解析速度更快、文件体积更小。一个典型的 `package-lock.json` 可能有几百 KB 甚至几 MB,而等价信息的 `bun.lock` 只有几十 KB。同时 Bun 的依赖解析算法在内存中操作依赖图,避免了 npm 反复读写 JSON 文件的 I/O 开销。
```bash
# 对比锁文件大小
du -h package-lock.json bun.lock
# 1.2M package-lock.json
# 45K bun.lock
```
锁文件的解析速度在实际使用中影响很大:每次 `npm install` 开始时都要先解析整个 `package-lock.json`,构建出完整的依赖树,然后才能开始下载。当锁文件有数 MB 时,仅 JSON 解析就可能需要几百毫秒。Bun 的二进制格式解析几乎不需要时间,整个依赖树在微秒级就能重建完成。
### 网络层优化
Bun 的 HTTP 客户端同样用 Zig 实现,支持 HTTP/2 多路复用和连接复用,下载包时可以在同一个 TCP 连接上并行传输多个文件,减少握手开销。npm 使用的 Node.js HTTP 客户端在大量并发请求时容易遇到连接限制和超时问题,尤其是安装包含数百个依赖的大型项目时。Bun 使用的 Zig HTTP 客户端则没有这些限制,可以更充分地利用网络带宽。
此外,Bun 支持自动安装功能:当你运行 `bun run` 时,如果代码中引用了尚未安装的依赖,Bun 会自动下载并安装,不需要手动执行 `bun install`。这进一步简化了开发流程——写完代码直接运行,依赖会自动处理。对比传统工作流:先 `npm install` 再 `node index.js`,Bun 合并为一步 `bun run index.ts`,省去了上下文切换的额外时间。
值得注意的是,Bun 的速度优势并不是单一的"银弹",而是多个层面的优化叠加效果。JSC 引擎的编译策略、Zig 语言的编译期能力、全局缓存的硬链接策略、并行生命周期脚本——每一项单独拿出来可能只快几倍,但组合在一起就产生了 25 倍甚至更大的性能差距。理解这些底层原理,不仅能帮助你在面试中回答好这个问题,更能指导你在实际项目中做出正确的技术选型:如果你的应用是短生命周期的脚本或 Serverless 函数,Bun 的启动优势非常明显;如果是长期运行的服务端应用,Node.js 的峰值性能和生态成熟度可能更值得依赖。
## 追问:Bun 的速度优势有代价吗?
有。JSC 的渐进式编译意味着长时间运行后峰值性能可能不如 V8 的 TurboFan 优化——V8 会在运行过程中持续分析和优化热点代码,最终生成的优化代码执行效率更高。不过对于大多数 Web 开发场景,启动速度的收益远大于峰值性能的微小差异。另外 Bun 生态成熟度仍不如 Node.js,部分 npm 包可能存在兼容问题,生产环境使用前需要充分验证。
前端5月28日 02:58
Bun 的 runtime 是如何设计的?和 Node.js 的事件循环有何不同?Bun 选用 JavaScriptCore 引擎 + Zig 语言实现运行时,采用"原生优先"架构绕过 libuv 抽象层,直接与操作系统 I/O 原语交互。Node.js 基于 V8 + libuv 的多阶段事件循环模型,两者在引擎选型、事件循环实现和性能表现上存在根本差异。
## 核心架构差异
Node.js 的执行链路:OS TCP → libuv 事件循环 → http_parser(C 绑定) → V8 JS 对象 → 处理函数
Bun 的执行链路:OS TCP → 原生 uWebSockets(C/Zig) → JavaScriptCore → 处理函数
Bun 少了一层 libuv 抽象,这是其性能优势的关键来源。具体差异:
| 维度 | Node.js | Bun |
|------|---------|-----|
| JS 引擎 | V8(Google) | JavaScriptCore(Apple) |
| 底层语言 | C/C++(libuv) | Zig(原生编译) |
| HTTP 实现 | libuv → http_parser → V8 | 原生 uWebSockets → JSC |
| 事件循环 | libuv 多阶段循环 | 原生优先,绕过 libuv 抽象 |
| 启动速度 | 基准 | 约 4 倍于 Node.js |
| 内存占用 | 较高 | 较低(JSC 内存效率更优) |
## Node.js 事件循环的六个阶段
Node.js 基于 libuv 实现单线程事件循环,主要阶段依次为:
1. **Timers** — 执行 setTimeout / setInterval 回调
2. **Pending callbacks** — 执行上一轮延迟的 I/O 回调
3. **Idle, prepare** — libuv 内部使用
4. **Poll** — 检索新 I/O 事件,执行 I/O 相关回调
5. **Check** — 执行 setImmediate 回调
6. **Close callbacks** — 处理 socket.on("close", ...) 等关闭事件
每个阶段有独立的队列,当前阶段队列清空或达到最大回调数后进入下一阶段。这种设计成熟稳定,但 libuv 作为中间层引入了额外开销,且单线程模型下 CPU 密集型任务会阻塞整个循环。
## Bun 的原生优先事件循环
Bun 不使用 libuv,而是用 Zig 原生实现事件循环核心逻辑:
- **原生 HTTP 服务器**:基于 uWebSockets 的 C/Zig 实现,直接与 OS TCP socket 交互
- **WebSocket 原生支持**:内置服务器和客户端,无需第三方库
- **自动打包和转译**:运行时内置打包器,TypeScript、JSX 开箱即用
代码示例:
```javascript
Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello from Bun!");
},
});
```
`Bun.serve` 不需要额外依赖,底层走原生实现路径,吞吐量约为 Node.js 的 3-4 倍。
## 性能对比的关键数据
- **HTTP 吞吐量**:Bun 约 52,000 req/s,Node.js 约 14,000 req/s,Bun.serve 比 Fastify 5 快约 2.8 倍
- **启动速度**:Bun 进程启动约为 Node.js 的 4 倍,Serverless 和 CI 场景收益明显
- **包安装**:Bun 内置包管理器利用硬链接和全局缓存,安装速度约为 npm 的 30 倍
## 兼容性与选型
Bun 对 Node.js API 兼容性已达 90% 以上,大部分 Express/Fastify 应用可直接运行,但需注意:
- 使用 N-API 的原生模块(如 node-pty)可能不兼容,因为它们深入 V8 内部或依赖 libuv
- 主线程都是单线程,CPU 密集型阻塞任务都会卡住事件循环,Bun 通过 `Bun.spawn` 和 Worker 线程支持并行
- 生产案例仍在积累,Node.js 有十五年的稳定性验证
选型建议:新项目和 CI/开发环境优先考虑 Bun;深度依赖 N-API 或对稳定性要求极高的生产环境暂用 Node.js。
## 追问:Bun 的事件循环是完全绕过了 libuv 还是部分替代?
Bun 并非完全重写 libuv 的全部功能,而是对核心 I/O 路径做了原生替代。文件系统操作等非热路径仍使用类似 libuv 的异步封装。本质上 Bun 的策略是"热路径原生优化,冷路径兼容复用"——HTTP、WebSocket 等高频 I/O 走原生 C/Zig 实现,低频操作保持与 Node.js 行为一致。理解这一点就能解释为什么 Bun 既能在基准测试中大幅领先,又能保持高兼容性:它只在关键路径上做了优化,没有试图从零重建整个异步 I/O 体系。前端5月28日 02:58
Bun 的包管理器 bun install 与 npm、yarn、pnpm 有什么区别?## 四大包管理器的定位
- **npm**:Node.js 官方默认包管理器,随 Node.js 安装,采用扁平化依赖树,生态最成熟但安装速度最慢。
- **yarn**:Facebook(现 Meta)开发,Yarn Classic(v1)已进入维护模式,Yarn Berry(v4)引入 Plug'n'Play 引擎,消除 `node_modules` 实现零安装。
- **pnpm**:基于硬链接和全局 store 机制,通过符号链接指向全局唯一的包副本,节省 50-70% 磁盘空间,严格隔离依赖防止幽灵依赖。
- **Bun**:由 Jarred Sumner 开发,使用 Zig 语言编写,Bun 既是 JavaScript 运行时也内置包管理器,`bun install` 是目前最快的包安装方案。
## 安装速度对比
速度是 `bun install` 最突出的优势。根据 2026 年的基准测试数据:
| 包管理器 | 50 个依赖耗时 | 800 个依赖 Monorepo 耗时 |
|----------|--------------|--------------------------|
| Bun | ~0.8 秒 | ~4.8 秒 |
| pnpm | ~4.2 秒 | ~28.6 秒 |
| yarn | ~6.8 秒 | ~52.3 秒 |
| npm | ~14.3 秒 | ~134.2 秒 |
Bun 能达到这个速度的原因:
- **Zig 编写**:Bun 用 Zig 实现文件系统操作和依赖解析,系统调用数量约为 npm 的 1/6(约 16.5 万次 vs npm 的 100 万次以上)。
- **全局缓存复用**:Bun 自动缓存已下载的包,重复安装时直接从本地缓存读取。
- **单进程架构**:不依赖 Node.js 运行时,避免了 Node.js 的多进程开销。
实际使用中,可以在已有 Node.js 项目中单独使用 `bun install` 替代 `npm install` 来加速安装,不需要切换到 Bun 运行时。
## 命令对比
四者的常用命令对照:
| 操作 | npm | yarn | pnpm | Bun |
|-------------|------------------|----------------|----------------|------------------|
| 安装全部依赖 | `npm install` | `yarn` | `pnpm install` | `bun install` |
| 添加依赖 | `npm install pkg`| `yarn add pkg` | `pnpm add pkg` | `bun add pkg` |
| 添加开发依赖 | `npm install -D pkg`| `yarn add -D pkg`| `pnpm add -D pkg`| `bun add -d pkg`|
| 删除依赖 | `npm uninstall pkg`| `yarn remove pkg`| `pnpm remove pkg`| `bun remove pkg`|
| 运行脚本 | `npm run dev` | `yarn dev` | `pnpm dev` | `bun run dev` |
| 执行包命令 | `npx pkg` | `yarn dlx pkg` | `pnpm dlx pkg` | `bunx pkg` |
| 锁文件 | `package-lock.json`| `yarn.lock` | `pnpm-lock.yaml`| `bun.lock` |
Bun 的命令设计与 npm 高度兼容,迁移成本低。注意 `bun add -d` 用小写 `d` 表示 dev 依赖,与 npm 的 `-D` 不同。
## 锁文件机制的差异
锁文件直接影响团队协作和 CI/CD 的可重复性:
- **npm**:`package-lock.json`,JSON 格式,可读性好但体积较大。
- **yarn**:`yarn.lock`,自定义文本格式,可读性一般。
- **pnpm**:`pnpm-lock.yaml`,YAML 格式,结构清晰。
- **Bun**:早期使用二进制格式 `bun.lockb`(不可读、不可 diff),从 Bun v1.2 起默认使用基于 JSONC 的文本格式 `bun.lock`,可读性和 git 友好度大幅提升。
迁移时需要注意:混合使用不同包管理器会导致生成多个锁文件,可能引起依赖版本不一致。建议在 `.npmrc` 或 `package.json` 中配置 `packageManager` 字段锁定使用的包管理器版本。
## 依赖存储策略
这是四者最本质的架构差异:
**npm**:扁平化安装,所有依赖铺平到 `node_modules` 根目录。优点是简单,缺点是允许访问未声明的依赖(幽灵依赖),且磁盘占用大。
**yarn Berry**:Plug'n'Play 模式下不生成 `node_modules`,用 `.pnp.cjs` 映射文件解析依赖路径。零安装模式下可以将依赖缓存在仓库中。
**pnpm**:全局 store + 硬链接。每个包只在全局 store 存一份,项目中的 `node_modules` 通过符号链接指向 store。在多个项目间共享依赖,磁盘占用最小。严格模式的依赖树防止幽灵依赖。
**Bun**:也使用全局缓存,但不像 pnpm 那样严格隔离依赖树。Bun 的策略更接近 npm 的扁平化风格,安装速度快但在防止幽灵依赖方面不如 pnpm。
## 兼容性与生产可用性
**npm 兼容性**:
Bun 的包管理器兼容 npm registry,可以直接安装 npm 上的包。截至 2026 年,Bun 对 npm 包的兼容率约 95-98%。主要不兼容的是依赖 `node-gyp` 编译的原生 C++ 模块(如 `bcrypt`、`canvas`、部分数据库驱动),这些包在 Bun 运行时下无法运行。
**关键限制**:
- 依赖 `node-gyp` 和 C++ 原生绑定的包不兼容 Bun 运行时。
- 部分 `postinstall` 脚本的行为与 Node.js 环境有差异。
- Windows 平台的兼容性仍弱于 macOS 和 Linux。
**但有一个重要细节**:你可以单独使用 `bun install` 作为包管理器而不切换到 Bun 运行时。这样既能享受安装速度提升,又能保持 Node.js 运行时的完全兼容。Anthropic、Vercel 等公司已在工具链中采用这种方式。
## 各场景下的选择建议
**新项目且追求速度**:Bun。初始化只需 `bun init`,安装依赖极快,开发体验流畅。
**大型 Monorepo 项目**:pnpm。严格的依赖隔离和 workspace 支持是管理大型项目的关键,磁盘节省在多包场景下尤为显著。Vue、Vercel、Prisma 等团队已全面采用 pnpm。
**需要零安装 / PnP**:yarn Berry。适合对安装确定性要求极高的场景,如 Docker 镜像构建。
**企业级稳定项目**:npm 或 pnpm。npm 11 增加了供应链安全特性(`min-release-age`、npm trust),pnpm 在速度和安全间取得了最佳平衡。
**混合方案**:在 Node.js 项目中用 `bun install` 替代 `npm install` 加速安装,运行时和构建仍用 Node.js。这是目前风险最低的 Bun 采纳方式。
## 总结
`bun install` 的核心价值是速度——在大型项目上比 npm 快 10-30 倍。但速度不是唯一考量:pnpm 在磁盘效率和依赖隔离上更优,yarn Berry 在零安装场景有独特优势,npm 在生态成熟度和安全性上领先。实践中,将 `bun install` 作为 Node.js 项目的包管理器替代方案是目前性价比最高的选择——保留 Node.js 运行时的兼容性,同时获得显著的安装速度提升。前端5月28日 02:54
Bun 为什么比 Node.js 快?底层架构与性能优化全解析Bun 是由 Jarred Sumner 创建的全能型 JavaScript 运行时,自 2022 年发布以来凭借惊人的启动速度和 HTTP 吞吐量迅速赢得开发者关注。官方基准测试显示,Bun 的启动时间仅 8-15ms,是 Node.js(60-120ms)的 5-12 倍;HTTP 请求处理达 11 万 QPS,远超 Node.js 的 4.5 万 QPS。2025 年底 Anthropic 收购 Bun 后,其冷启动优势成为 Claude Code 等 AI 工具的核心基础设施。这种高性能并非偶然,而是底层架构设计哲学的直接产物:**选用 JavaScriptCore 替代 V8、用 Zig(后迁移 Rust)实现零开销抽象、将运行时/包管理/打包/测试四合一**。本文将拆解 Bun 性能优势的每一个技术支柱。
## JavaScriptCore:比 V8 更快的启动引擎
Bun 性能优势的第一根支柱是选择了 Apple 的 JavaScriptCore(JSC)而非 Google 的 V8 作为 JavaScript 引擎。这个选择直接决定了 Bun 的启动速度优势。
JavaScriptCore 是 Safari 的 JS 引擎,与 Chrome/Node.js 使用的 V8 有着根本不同的优化策略:
- **更快的启动编译**:JSC 的解释器和 JIT 编译器启动开销远低于 V8。V8 需要先解释执行代码、收集类型反馈,再由 TurboFan 编译优化,这个过程在短生命周期脚本中成本高昂。JSC 则采用更轻量的分层编译策略,冷启动更快。
- **更低的内存基线**:JSC 的初始内存占用比 V8 低约 30%。对于 Serverless 场景中频繁冷启动的函数,这意味着更少的资源浪费和更低的费用。
- **Safari Web API 原生复用**:Bun 直接复用了 JSC 中已实现的 Web 标准 API(如 fetch、WebSocket、ReadableStream),避免了从零实现的工程成本和性能损耗。
Bun 的作者 Jarred Sumner 明确表示,选用 JavaScriptCore 是 Bun 速度的两大关键因素之一。
## Zig 语言:手动内存管理与编译期计算
Bun 最初完全用 Zig 编写,这是性能优势的第二根支柱。Zig 的选择并非追逐潮流,而是基于以下技术考量:
### 手动内存管理消除 GC 暂停
Zig 没有隐式垃圾回收,所有内存分配由开发者显式控制。Bun 团队为此编写了高度优化的自定义分配器:
- **热路径零分配**:在 HTTP 请求处理、文件 I/O 等关键路径上,Bun 通过预分配内存池和对象复用避免了运行时动态分配,消除了 GC 暂停。
- **精确的内存生命周期**:Zig 的 `defer` 和 `errdefer` 语法让资源释放时机一目了然,内存泄漏风险远低于手动 `malloc/free`。
### Comptime 编译期代码生成
Zig 的 `comptime` 能力是性能利器——在编译期执行代码并生成高度特化的机器码:
```zig
// 编译期生成特化的解析函数
fn Parser(comptime T: type) type {
return struct {
pub fn parse(input: []const u8) ?T {
// 编译期根据 T 生成专用解析逻辑
}
};
}
```
Bun 的 JSX/TypeScript 转译器大量使用 comptime 特化,避免运行时分支判断,这是其转译速度远超 Babel/swc 的原因之一。
### 与 C 生态的无缝互操作
Zig 可以直接 `@cImport` C 头文件而无需 FFI 绑定层。Bun 利用这一点直接调用 JavaScriptCore 的 C API,调用开销接近零,而 Node.js 通过 N-API 调用 C++ 则需要经过多层封装。
## 四合一架构:消除进程间通信开销
Bun 将运行时、包管理器(bun install)、打包器(bun build)、测试运行器(bun test)整合在单一二进制文件中,这是性能优势的第三根支柱。
传统 Node.js 开发需要 Node.js + npm/yarn + webpack/esbuild + Jest 多个进程协同工作:
- **进程启动开销**:每个工具独立启动,重复加载 V8 和基础库。
- **IPC 通信成本**:工具间通过管道或临时文件传递数据,序列化/反序列化开销显著。
- **重复解析**:TypeScript 代码被不同工具反复解析多次。
Bun 的四合一架构让所有工具共享同一个 JavaScriptCore 实例和 AST:
```bash
# 一个二进制完成所有工作
curl -fsSL https://bun.sh/install | bash
bun init # 初始化项目
bun install # 安装依赖(比 npm 快 30 倍)
bun test # 运行测试(比 Jest 快 3 倍)
bun build ./src/index.ts --outdir=dist # 打包
```
包管理器的性能差异尤为明显:Bun install 对中型项目的冷安装约 1 秒,而 npm 需要 20 秒。原因在于 Bun 仅执行约 16.5 万次系统调用,而 npm 执行近 100 万次,同时 Bun 使用硬链接避免重复下载。
## 内置数据库与 I/O 优化
Bun 1.2 引入了内置数据库支持,直接在运行时层面优化 I/O:
### bun:sqlite 同步 API
Bun 内嵌了 SQLite 并提供同步 API。在 Node.js 中访问 SQLite 需要通过 `better-sqlite3` 的 C++ 绑定跨越 JS/C++ 边界,而 Bun 的 SQLite 操作直接在 Zig 层完成:
```typescript
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
// 同步调用,无 Promise 开销
const user = stmt.get(42);
```
### 内置 PostgreSQL 与 S3 客户端
Bun 1.2+ 还内置了 `Bun.SQL`(PostgreSQL 客户端)和 S3 客户端,减少了外部依赖和网络中间层。
## Zig 到 Rust 的迁移:2026 新篇章
2025 年 12 月 Anthropic 收购 Bun 后,团队启动了从 Zig 到 Rust 的大规模迁移。2026 年 5 月,68% 的代码库(96 万行)在 6 天内完成重写,达到 99.8% 测试兼容性。
迁移的技术动机:
- **Rust 的所有权模型**消除手动内存管理导致的内存泄漏问题。重写后 Bun 运行时中的内存安全缺陷大幅减少。
- **AI 辅助开发更友好**:Anthropic 的 AI 工具对 Rust 的理解和生成能力远强于 Zig,这是迁移的重要推动力。Zig 社区的反 AI 立场与 Anthropic 的技术路线冲突。
- **生态与社区**:Rust 拥有更成熟的工具链(Cargo)和更丰富的库生态,有利于长期维护。
迁移后的性能表现:HTTP 吞吐量提升至 Node.js 的 4 倍,但社区也对 AI 快速重写引入的 13000+ 个 `unsafe` 块和部分测试修改提出了质量担忧。
## 实战:Bun 性能优化的最佳实践
### 适合 Bun 的场景
- **Serverless / Edge Function**:冷启动 8-15ms,是 Node.js 的 1/10,直接降低云函数延迟和费用。
- **高频 HTTP 服务**:11 万 QPS 的吞吐量适合 API 网关和微服务。
- **CI/CD 流水线**:`bun install` 比 npm 快 30 倍,`bun test` 比 Jest 快 3 倍,显著缩短构建时间。
- **TypeScript 开发**:零配置运行 TS,无需 `ts-node` 或 `tsc` 预编译。
### 注意事项
- **生态兼容性**:Bun 通过了 98% 的 Node.js 测试套件,但少数 N-API 原生模块可能存在兼容问题,迁移前需验证。
- **内存模型差异**:Bun 的内存管理策略与 Node.js 不同,长时间运行的进程需关注内存增长趋势,可使用 `Bun.gc()` 手动触发回收。
- **Rust 迁移过渡期**:当前处于 Zig→Rust 迁移期,部分功能可能存在两个实现并行的情况,建议关注官方发布日志。
### 快速上手
```bash
# 安装
curl -fsSL https://bun.sh/install | bash
# 创建项目
bun init
# 运行 TypeScript
bun run src/index.ts
# 启动 HTTP 服务
bun run server.ts # 支持 Bun.serve()
# 性能基准测试
bun bench
```
Bun 证明了 JavaScript 运行时可以同时做到快速启动、高吞吐和低内存占用——关键在于从引擎选择、实现语言到架构设计的每一层都做出有利于性能的决策。随着 Rust 迁移的推进,Bun 正从实验性工具走向生产级基础设施。前端5月28日 02:46
Bun 能跑你的 TypeScript 项目吗?特性兼容性详解Bun 基于 JavaScriptCore 引擎(Safari 同款),原生支持运行 JavaScript 和 TypeScript,不需要 Babel、tsc 或任何转译工具。它覆盖了 ES2020 至 ES2025 的全部 ECMAScript 标准,兼容约 98% 的 Node.js API,绝大多数 Node 项目直接 `bun run` 就能跑。
## JavaScript 特性
Bun 的 JS 引擎紧跟 Safari 的 JavaScriptCore 更新,当前版本支持到 ES2025 全部稳定特性:
**ES2020+**:`BigInt`、`Promise.allSettled`、`Optional Chaining`(`?.`)、`Nullish Coalescing`(`??`)——这些已经是基础能力,三个主流运行时都支持。
**ES2022-2023**:`Array.at()`、`Object.hasOwn()`、Top-level `await`、`Array.findLast()`、`Array.toSorted()` / `toReversed()` / `toSpliced()`——注意 `toSorted` 这类非变异方法在函数式编程中很实用,Bun 开箱即用。
**ES2024-2025**:`Promise.withResolvers()`、`Object.groupBy()`、`Map.groupBy()`、`String.isWellFormed()`、正则表达式 `/v` 标志。Bun v1.3.10 起完整支持 TC39 Stage 3 的 ES Decorators 标准规范,包括 `accessor` 关键字、`Symbol.metadata` 和 `ClassFieldDecoratorContext` API——不再只是 TypeScript 的 `experimentalDecorators`,而是语言层面的装饰器。
**模块系统**:原生 ESM,`import` / `export` 直接可用,同时也兼容 CommonJS 的 `require()`——Bun 会自动处理两种模块系统的互导,不需要 `.mjs` / `.cjs` 扩展名区分。
**Web API**:`fetch`、`WebSocket`、`URL`、`URLSearchParams`、`TextEncoder` / `TextDecoder`、`ReadableStream`、`AbortController` 等浏览器 API 全部内置,写服务端代码时不再需要 `node-fetch` 这类 polyfill。
## TypeScript 特性
Bun 内置 TypeScript 支持,`.ts` / `.tsx` 文件直接 `bun run` 即可执行,转译速度约 10ms:
- **类型系统完整**:泛型、类型守卫、条件类型、映射类型、模板字面量类型全部支持
- **JSX / TSX**:零配置支持,React 项目不需要配置 Babel
- **装饰器双模式**:同时支持 TC39 标准 ES Decorators 和 TypeScript 的 `experimentalDecorators`(在 `tsconfig.json` 中配置)
- **路径别名**:`tsconfig.json` 的 `paths` 配置自动生效,不用装 `tsconfig-paths`
- **枚举和命名空间**:TypeScript 独有语法,Bun 全部支持,无需 `isolatedModules` 限制
一个关键区别:**Bun 只做转译,不做类型检查**。这意味着类型错误不会阻止代码运行——开发时靠 IDE 实时提示,CI 中用 `tsc --noEmit` 做类型检查。这是刻意的设计选择,用 10ms 的转译速度换取开发体验。
推荐的 `tsconfig.json` 配置:
```json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"]
}
}
```
安装类型定义:`bun add -d @types/bun`,这样 `Bun.file()`、`Bun.serve()` 等 API 都有完整的类型提示。
## Bun 独有 API
Bun 不只是"另一个 Node.js",它提供了一套更简洁的原生 API,很多是 Node.js 需要装第三方包才能实现的能力:
**文件操作** — `Bun.file(path)` 返回一个 `Blob` 引用(不立即读内存),`Bun.write(path, content)` 写入文件。比 `fs.readFile` 快 3-10 倍,因为底层用了更高效的系统调用。
**HTTP 服务器** — `Bun.serve()` 内置 HTTP/1.1、HTTP/3 QUIC、WebSocket、SSE 支持,Bun 1.3 起还支持前端热重载。一个函数搞定 API 服务 + 静态文件 + WebSocket。
**数据库** — `Bun.SQL` 是统一的 SQL 客户端,支持 Postgres、MySQL、MariaDB、SQLite,零外部依赖。用 tagged template literal 写查询,自带参数化防注入:
```typescript
import { sql } from "bun";
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
```
**对象存储** — `Bun.S3Client` 内置 S3 兼容操作,支持上传、下载、列表、存储类配置。
**测试** — `bun test` 是 Jest 兼容的测试运行器,速度是 Jest 的 5-20 倍,支持快照测试、并行执行、JUnit 报告输出。
## 和 Node.js 的关键差异
| 方面 | Bun | Node.js |
|------|-----|---------|
| JS 引擎 | JavaScriptCore | V8 |
| TS 支持 | 内置,零配置 | v22.6+ 实验性支持 |
| ESM | 原生优先,兼容 CJS | CJS 优先,ESM 需配置 |
| 包管理器 | `bun install` | `npm` / `yarn` / `pnpm` |
| 测试 | 内置 `bun test` | 需安装 Jest / Vitest |
| HTTP 服务器 | 内置 `Bun.serve()` | 需安装 Express / Fastify |
| 数据库客户端 | 内置 | 需安装 pg / mysql2 |
| Node API 兼容 | ~98% | 原生 |
| 长时间运行 GC | 较新,仍在优化 | V8 GC 成熟稳定 |
**不兼容的场景**:原生 C++ addon(node-gyp)因为引擎不同无法直接运行——`canvas`(依赖 cairo)、`better-sqlite3`(依赖 node-gyp)这类包需要找纯 JS 替代或等 Bun 的原生方案。Bun 1.3 已经内置了 SQLite 客户端,`better-sqlite3` 的场景可以直接用 `Bun.SQL` 替代。
## 追问
### Bun 的 TS 支持和 Deno 有什么区别?
两者都原生支持 TS。核心区别:Bun 追求 Node.js 兼容和速度,只做转译不做类型检查;Deno 可以做运行时类型检查(`deno check`),而且有权限安全模型。从 Node 迁移选 Bun(兼容性好),从零开始重视安全选 Deno。实际开发中两者都能跑主流框架,选哪个更多取决于团队偏好。
### 迁移 Node 项目到 Bun 踩过什么坑?
三个最常见的坑:1) C++ 原生模块跑不了——`bcrypt` 换 `bcryptjs`,`canvas` 看看能不能用 `sharp` 替代;2) `process.env` 读取时机——Bun 的 env 注入方式略有不同,某些在模块顶层读环境变量的代码可能行为不一致;3) `__dirname` 和 `__filename`——Bun 中推荐用 `import.meta.dir` 和 `import.meta.file` 替代,CJS 兼容模式下也能用但 ESM 下不行。迁移前跑一遍 `bun test`,全过再切 `bun run`。
### Bun 适合生产环境吗?
2026 年可以了。Bun 通过了 90%+ 的 Node.js 测试套件,Anthropic 2025 年底收购后投入加大,Vercel 已认证 Bun 平台,约 20% 的新 Next.js 部署跑在 Bun 上。但如果你的服务是 72 小时+的长驻进程,V8 的 GC 在这类场景更成熟,建议压测后再决定。
### 为什么 Bun 比 Node.js 快?
三个原因:JavaScriptCore 引擎启动更快(牺牲了一点 JIT 长期优化换启动速度);核心模块用 Zig 写,减少了 JS/C++ 边界调用开销;模块解析用全局缓存,不用每次遍历 `node_modules` 查找。结果就是"启动"和"IO 密集"场景优势最大(包安装快 10-30 倍、HTTP 吞吐量 2-3 倍),纯 CPU 计算和 Node 差距不大。
### Bun 和 Node.js 的 ESM 处理有什么不同?
Bun 原生 ESM 优先——`import` 语句直接解析,不需要 `package.json` 加 `"type": "module"`,也不需要 `.mjs` 扩展名。`require()` 在 Bun 的 ESM 文件中也能用(Bun 自动处理互导)。Node.js 则是 CJS 优先,ESM 需要显式配置,而且 `require()` 不能在 ESM 文件中使用。这个差异在迁移时最容易被忽略。前端5月28日 00:19
如何在 Bun 中进行代码覆盖率统计?## 基本用法
Bun 内置了覆盖率收集器,无需额外安装 Istanbul 或 c8 等工具。运行测试时加上 `--coverage` 参数即可:
```bash
bun test --coverage
```
执行后会在控制台输出覆盖率报告表格:
```
-------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files | 66.67 | 77.78 |
math.ts | 50.00 | 66.67 | 8-12
random.ts | 50.00 | 66.67 | 5-9
-------------|---------|---------|-------------------
```
报告包含三个核心指标:**函数覆盖率**(% Funcs)、**行覆盖率**(% Lines)和**未覆盖行号**(Uncovered Line #s),让你一眼看到哪些代码路径没有被测试到。
需要注意,Bun 只统计测试执行期间实际被 import/load 的文件。如果一个模块从未被任何测试导入,它不会出现在覆盖率报告中——这是很多开发者踩的坑。对于未被直接导入的工具模块,建议在测试文件中动态 import 确保其被加载。
## 在 bunfig.toml 中配置覆盖率
Bun 使用 `bunfig.toml`(注意不是 `.bunrc`,也不是 `package.json`)管理项目配置,覆盖率相关的所有选项都可以集中配置:
```toml
[test]
coverage = true # 默认启用覆盖率
coverageReporter = ["text", "lcov"] # 输出格式:text 控制台、lcov 文件
coverageDir = "./coverage" # 报告输出目录,默认 coverage
coverageSkipTestFiles = true # 排除测试文件本身的覆盖率
coveragePathIgnorePatterns = [ # 忽略指定路径
"**/*.spec.ts",
"src/generated/**",
"*.config.js"
]
```
其中 `coverageSkipTestFiles = true` 可以将 `*.test.ts` 等测试文件从覆盖率统计中排除,避免测试代码本身干扰结果——这在实际项目中经常需要,否则覆盖率数据会被测试辅助代码"稀释"。`coveragePathIgnorePatterns` 则用于排除生成代码、配置文件等不需要覆盖的路径。
CLI 参数始终优先于 `bunfig.toml` 配置,临时调整时直接在命令行覆盖即可。
## 覆盖率阈值与质量门禁
设置覆盖率阈值是保证代码质量的有效手段。Bun 支持在 `bunfig.toml` 中配置阈值,一旦覆盖率低于设定值,测试将失败退出(非零退出码),适合在 CI 中作为质量门禁。
**统一阈值**(同时应用于 lines、functions、statements):
```toml
[test]
coverageThreshold = 0.8
```
**分维度阈值**(更精细的控制):
```toml
[test]
coverageThreshold = { lines = 0.85, functions = 0.80, statements = 0.75 }
```
设置了 `coverageThreshold` 后,Bun 会自动启用 `fail_on_low_coverage` 行为。建议从较低的阈值(如 60%)开始,逐步提高,而不是一上来就要求 90% 以上。过高的阈值会导致团队为达标而写无意义的测试,反而降低代码质量。
有一个已知行为需要注意:`coverageThreshold` 是按单个文件检查的,即使项目整体覆盖率达标,某个文件不达标也会失败。如果某些文件覆盖率暂时无法达标,可以将其加入 `coveragePathIgnorePatterns` 排除。
## 生成 LCOV 报告与 CI 集成
LCOV 是覆盖率报告的通用格式,Codecov、Coveralls 等服务都支持。Bun 可以直接生成 LCOV 报告:
```bash
bun test --coverage --coverage-reporter=lcov
```
生成的 `coverage/lcov.info` 文件可以上传到覆盖率服务。以下是 GitHub Actions 的完整集成示例:
```yaml
name: Test with Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun test --coverage --coverage-reporter=lcov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
```
也可以同时输出多种格式,兼顾本地查看和 CI 上传:
```bash
bun test --coverage --coverage-reporter=text --coverage-reporter=lcov
```
这样本地开发时能在终端快速看到摘要,CI 环境中又能自动上传 LCOV 到代码覆盖率平台,在 PR 页面直接展示覆盖率变化趋势。
## 实战示例:从零搭建覆盖率统计
项目结构:
```
project/
├── src/
│ └── math.ts
├── test/
│ └── math.test.ts
└── bunfig.toml
```
**步骤一:编写源码** `src/math.ts`:
```typescript
export function add(a: number, b: number): number {
return a + b;
}
export function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
```
**步骤二:编写测试** `test/math.test.ts`:
```typescript
import { test, expect } from "bun:test";
import { add, divide } from "../src/math";
test("add two numbers", () => {
expect(add(1, 2)).toBe(3);
});
test("divide two numbers", () => {
expect(divide(6, 3)).toBe(2);
});
test("divide by zero throws", () => {
expect(() => divide(1, 0)).toThrow("Division by zero");
});
```
**步骤三:配置 bunfig.toml**:
```toml
[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageSkipTestFiles = true
coverageThreshold = { lines = 0.8, functions = 0.8 }
```
**步骤四:运行并查看结果**:
```bash
bun test --coverage
```
输出示例:
```
✓ add two numbers
✓ divide two numbers
✓ divide by zero throws
-------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files | 100.00 | 100.00 |
math.ts | 100.00 | 100.00 |
-------------|---------|---------|-------------------
```
如果后续新增了 `subtract` 函数但没有对应测试,覆盖率会下降,低于阈值时测试直接失败,提醒你补充测试。
## 常见问题
**覆盖率报告为空或缺少文件**
Bun 只追踪测试期间被加载的文件。确保所有源码模块都被测试文件直接或间接导入。对于工具类文件,可以在测试中添加动态导入:
```typescript
test("ensure utils loaded", async () => {
await import("../src/utils");
});
```
**覆盖率报告中出现测试文件本身**
设置 `coverageSkipTestFiles = true` 即可排除。默认情况下测试文件会被计入覆盖率,导致统计结果失真。
**想只跑部分测试的覆盖率**
可以指定测试文件或按名称过滤:
```bash
bun test --coverage src/components/*.test.ts
bun test --coverage --test-name-pattern="API"
```前端5月28日 00:16
Bun 为什么选择 Zig 作为底层语言?Bun 选择 Zig 作为底层语言,核心原因有三:
**1. 与 C 的零开销互操作。** Bun 底层依赖 WebKit 的 JavaScriptCore(JSC)引擎,这是一个纯 C/C++ 库。Zig 可以直接 `@cImport` C 头文件,编译器自动解析 C 类型并生成 Zig 绑定,无需手写 FFI 胶水代码或使用 bindgen 工具。调用 C 函数就像调用原生函数一样。对比 Rust 调用 C 需要写 `unsafe` 块、手动管理 FFI 边界、处理类型映射,Zig 的方式减少了跨语言调用的性能损耗和维护成本。对于 Bun 这种深度依赖 C/C++ 库的项目,这一优势是决定性的。
```zig
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf("Hello from C\n");
}
```
**2. 无隐藏控制流,性能完全可预测。** Zig 语言设计上没有任何隐藏行为——没有隐式内存分配、没有异常抛出、没有运算符重载背后的秘密调用、没有默认初始化。代码做什么就是什么,性能行为 100% 可预测。这对构建 JavaScript 运行时至关重要:运行时本身不能有不可控的延迟或隐式分配,否则会直接影响上层 JS 代码的执行稳定性和内存占用。
**3. Comptime 编译时计算。** Zig 的 `comptime` 特性允许将代码标记为编译期执行,能力远超 C++ 的 `constexpr`——不仅可以计算常量表达式,还能在编译期执行类型操作、控制流和函数调用,动态生成类型和函数。Bun 利用 comptime 在编译期完成类型检查和代码生成优化,将运行时开销前置到构建阶段,从而在启动速度上获得显著收益。实测 Bun 在 Linux 上启动速度比 Node.js 快 4 倍,comptime 功不可没。
此外,Zig 没有垃圾回收器。Bun 的 JSC 已自带 GC,再叠加一层 GC 会造成双重内存管理的浪费和不可控的 GC 暂停。Zig 的手动内存管理配合 `defer` 关键字确保资源释放,让 Bun 团队能精确控制内存分配,实现近乎零开销的 HTTP 请求处理和文件操作。
值得注意的是,2026 年 5 月 Bun 团队已宣布从 Zig 迁移至 Rust(96 万行代码重写,测试兼容性达 99.8%),主要原因是项目规模化后 Zig 生态的局限性(库不丰富、社区规模有限、招聘困难)和长期内存泄漏问题。但当初选择 Zig 的技术逻辑依然成立:小团队快速原型阶段,Zig 的低摩擦和 C 互操作是加速器。
## 追问
**Zig 的内存管理与 Rust 的借用检查器有什么本质区别?**
Zig 采用手动内存管理,开发者自行决定分配和释放时机,编译器不做所有权检查,靠 `defer` 和 Allocator 接口规范资源管理。Rust 的借用检查器在编译期强制执行所有权规则,代码能编译就意味着数据竞争和悬垂指针不会发生。Zig 更灵活但更依赖开发者自律,Rust 更安全但有学习曲线。Bun 迁移到 Rust 的核心原因就是用编译时保证替代人工自律来消除内存泄漏。
**Bun 的性能优势主要来自 Zig 还是架构设计?**
主要来自架构设计。Bun 使用 JSC 而非 V8、内置 HTTP 服务器省去 libuv 中间层、集成本地打包器消除工具链切换开销,这些架构决策才是性能差异的根本。Zig 在系统级操作(文件 I/O、网络)上提供了高效实现,但 CPU 密集型任务的性能主要取决于 JSC 引擎。这也是为什么迁移到 Rust 后,Bun 的性能不会有数量级变化——架构没变,语言换了,性能特征基本保持。
**如果现在要做类似的 JS 运行时,还应该选 Zig 吗?**
视情况而定。如果团队小、需要快速原型验证、与 C 库深度交互,Zig 仍是好选择。如果追求长期维护性和生态成熟度,Rust 更稳妥。Bun 的迁移历程说明:原型阶段 Zig 的低摩擦和 C 互操作是加速器,规模化后 Rust 的类型安全保证和生态优势是更可持续的选择。这是一个典型的"用 Zig 验证,用 Rust 工程化"的技术演进路径。前端5月28日 00:15
Bun 如何优化内存管理?和 Node.js 的 GC 有何不同?Bun 和 Node.js 在内存管理上采用了截然不同的技术路线。Node.js 依赖 V8 引擎的分代垃圾回收机制,成熟稳定但在高并发下存在长暂停问题;Bun 则基于 JavaScriptCore 引擎(WebKit/Safari 的 JS 引擎),配合 Zig 原生层的内存优化,走出了另一条路径。理解两者 GC 机制的差异,是选择运行时和处理内存密集型任务的关键依据。
## Bun 的内存管理机制
Bun 的核心架构是用 Zig 语言编写的运行时,JavaScript 执行层则依赖 JavaScriptCore(JSC)引擎。JSC 的垃圾回收器与 V8 有本质区别,Bun 还在此基础上做了多层优化。
### JavaScriptCore 的分代 GC
JSC 采用分代垃圾回收策略,将堆分为多个区域:
- **Eden 区**:存放新创建的对象,回收频率高、速度快。JSC 的 Eden 区采用半空间复制算法(Semi-Space Copy),将存活对象从一个半空间复制到另一个,实现快速清理。
- **Old Space**:经过多次 GC 仍存活的对象晋升到此区域,采用标记-清除(Mark-Sweep)算法回收。
- **IsoSubspaces**:JSC 的独特设计,为相同类型的对象分配独立的堆区域。同类型对象集中存放不仅减少碎片,还让 GC 在扫描时能跳过不相关的区域,提升回收效率。
### Zig 原生层的内存优化
Bun 在 JSC 之外,通过 Zig 层引入了额外的内存管理手段:
- **Mimalloc 分配器**:Bun 使用 Mimalloc 作为原生内存分配器,替代系统默认的 malloc。Mimalloc 的碎片率更低,内存归还操作系统的速度更快。Bun v1.2.2 版本升级了内存分配器,额外减少了 5% 的内存占用。
- **手动内存管理**:Zig 本身没有 GC,Bun 的原生代码通过 Zig 的显式内存管理(defer/errdefer)精确控制资源的分配和释放,避免隐式开销。
- **跨语言引用类型**:Bun 在 JSC 的 JS 对象和 Zig 的原生资源之间建立了多种引用类型(如 BunString、JSValue),确保对象在不同语言边界上的生命周期正确管理。
### Bun.gc() API
Bun 提供了手动触发 GC 的接口:
```javascript
// 强制触发完整垃圾回收
Bun.gc(true);
// 触发增量回收(更轻量,不阻塞主线程)
Bun.gc();
```
与 Node.js 需要通过 `--expose-gc` 启动标志才能使用 `global.gc()` 不同,Bun 的 `Bun.gc()` 默认可用。这在需要精确控制回收时机的场景(如批处理任务的间隙)中非常实用。
## Node.js 的垃圾回收机制
Node.js 基于 V8 引擎,使用成熟的分代垃圾回收器。理解其工作机制有助于对比 Bun 的差异。
### 分代 GC 的工作原理
- **新生代(Young Generation)**:采用 Scavenge 算法(半空间复制),将堆分为两个等大的半空间(From/Semi-Space 和 To/Semi-Space)。新对象分配在 From 空间,GC 时将存活对象复制到 To 空间,然后交换两个空间的角色。对象在经历两次 Scavenge 后晋升到老生代。
- **老生代(Old Generation)**:使用 Mark-Sweep-Compact 算法。标记阶段遍历所有可达对象,清除阶段回收不可达对象占用的空间。当碎片率过高时触发压缩(Compact),移动存活对象使内存连续。
- **增量标记(Incremental Marking)**:V8 将标记任务拆分为多个小步骤,穿插在 JavaScript 执行之间,减少单次暂停时间。但完整标记仍需要 Stop-the-World。
### V8 GC 的局限
- **Full GC 暂停**:当老生代空间不足时,V8 可能触发 Full GC,暂停时间可达数十到上百毫秒。在实时应用(如 WebSocket 长连接服务)中,这种暂停会直接导致请求超时。
- **内存碎片**:Mark-Sweep 不移动对象,长期运行后老生代碎片率升高(可达 10-15%)。虽然 Compact 可以解决碎片,但本身也会引起暂停。
- **静态预分配**:Node.js 默认的堆大小限制需要通过 `--max-old-space-size` 手动设置,无法根据运行时负载自动调整,容易造成过度分配或内存不足。
```javascript
// Node.js 中调整堆大小
// 启动时:node --max-old-space-size=4096 app.js
// 手动触发 GC(需 --expose-gc 启动标志)
if (global.gc) {
global.gc();
}
```
## 核心差异对比
| **特性** | **Bun** | **Node.js (V8)** |
| --- | --- | --- |
| **JS 引擎** | JavaScriptCore (WebKit) | V8 (Chromium) |
| **GC 算法** | 分代 GC + IsoSubspaces | 分代 GC (Scavenge + Mark-Sweep-Compact) |
| **原生层** | Zig + Mimalloc | C++ + libuv |
| **手动 GC** | `Bun.gc()` 默认可用 | `global.gc()` 需 `--expose-gc` |
| **内存分配器** | Mimalloc(低碎片) | V8 内置分配器 |
| **堆暂停** | 增量回收,暂停较短 | Full GC 时暂停较长 |
### 实测数据参考
基于社区基准测试(Bun v1.1.x vs Node.js v20.x),典型场景下的内存表现:
- **空闲状态**:Bun 约 15-20 MB,Node.js 约 30-35 MB。Bun 的基础开销约为 Node.js 的一半。
- **中等负载**(REST API 服务):Bun 约 100-130 MB,Node.js 约 180-220 MB,Bun 少约 40-45%。
- **GC 暂停频率**:Bun 的增量回收策略使暂停更短更频繁,单次暂停通常在 10ms 以内;Node.js 的 Full GC 暂停可达 50-100ms,但触发频率较低。
这些差异源于 JSC 和 V8 不同的设计哲学:JSC 倾向于更频繁但更小的回收周期,牺牲少量 CPU 换取更平滑的内存曲线;V8 则倾向于积累更多垃圾后一次性回收,在吞吐量上有优势。
## 实践建议
### 选择 Bun 的场景
- **实时服务**:WebSocket、SSE 等对延迟敏感的应用,Bun 的短暂停特性更合适。
- **内存受限环境**:容器化部署中,Bun 的低内存占用允许更小的实例规格。
- **脚本和工具链**:Bun 的启动速度快(约为 Node.js 的 4 倍),适合 CLI 工具和构建脚本。
```javascript
// Bun: 批处理任务间隙手动回收
for (const batch of batches) {
await processBatch(batch);
Bun.gc(); // 每批处理后回收,保持内存稳定
}
```
### 选择 Node.js 的场景
- **长期稳定运行的生产服务**:V8 的 GC 经过十余年优化,极端场景下的行为更可预测。
- **成熟生态依赖**:大量 npm 包针对 Node.js 做了优化和测试,迁移成本需评估。
- **GC 调优需求**:V8 提供丰富的 GC 调优参数(`--max-old-space-size`、`--gc-interval`、`--trace-gc`),调试工具链更完善。
```javascript
// Node.js: GC 调优示例
// 启动参数
// node --max-old-space-size=4096 --trace-gc app.js
// 使用 clinic.js 分析内存
// npx clinic heapprofile -- node app.js
```
### 通用内存优化技巧
- **用 WeakRef 管理缓存**:两个运行时都支持 WeakRef,适合实现不阻止 GC 的缓存。
```javascript
const cache = new Map();
function getCached(key, compute) {
const ref = cache.get(key);
if (ref) {
const val = ref.deref();
if (val !== undefined) return val;
}
const result = compute();
cache.set(key, new WeakRef(result));
return result;
}
```
- **大文件顺序处理**:避免一次性读入大量数据,用流式或分批处理减少内存峰值。Bun 和 Node.js 都支持流式 API。
- **监控内存使用**:两个运行时都提供 `process.memoryUsage()` 接口,建议在关键路径上采集指标。
Bun 和 Node.js 的内存管理各有侧重:Bun 以低内存占用和短暂停见长,Node.js 以成熟稳定和丰富的调优工具取胜。选择时应基于项目对延迟、内存效率和生态成熟度的实际需求,而非简单的性能数字对比。前端5月28日 00:14
Bun 的日志和错误处理机制如何?Bun 作为基于 JavaScriptCore 引擎的高性能运行时,在日志和错误处理方面既保持了与 Node.js 的兼容性,又提供了自己的特色实现。下面从日志 API、错误捕获、服务端错误处理三个方面展开。
## 日志机制:console API 与配置增强
Bun 的日志系统以标准 `console` API 为核心,完全兼容浏览器和 Node.js 的用法:
* **标准 console 方法**:Bun 支持 `console.log()`、`console.error()`、`console.warn()`、`console.info()`、`console.debug()` 等全部标准方法。输出格式与 Node.js 一致,开发者无需修改现有代码即可迁移。
* **对象检查深度可配置**:Bun 允许通过 `bunfig.toml` 或 CLI 参数调整 `console.log()` 输出嵌套对象的深度,默认为 2 层:
```toml
# bunfig.toml
console.depth = 4
```
或通过命令行指定:
```bash
bun --console-depth 4 run index.ts
```
这对于调试深层嵌套对象非常实用,避免在 Node.js 中频繁使用 `JSON.stringify` 的繁琐操作。
* **Bun.inspect 精细化输出**:Bun 提供 `Bun.inspect()` 方法,支持语法高亮的格式化输出,特别适合错误对象的详细展示:
```javascript
const err = new Error("Something went wrong");
console.log(Bun.inspect(err, { colors: true }));
```
`Bun.inspect` 会输出错误消息、堆栈跟踪以及出错位置的源代码预览,比 Node.js 默认的 `console.log(err)` 提供更丰富的上下文信息。
* **stdout/stderr 作为 BunFile**:Bun 将标准输出和标准错误暴露为 `Bun.stdout` 和 `Bun.stderr`,类型为 `BunFile`。这意味着可以直接用文件操作 API 写入日志:
```javascript
await Bun.write(Bun.stdout, "自定义日志输出\n");
```
* **调试环境变量**:通过设置 `BUN_CONFIG_VERBOSE_FETCH=1`,Bun 会自动记录所有 `fetch()` 和 `node:http` 发出的网络请求,方便排查网络问题,无需手动添加日志:
```bash
BUN_CONFIG_VERBOSE_FETCH=1 bun run server.ts
```
## 错误处理机制:堆栈跟踪与服务端容错
Bun 的错误处理建立在 JavaScript 标准异常模型之上,但在堆栈跟踪和服务端错误边界方面有独特的实现。
* **V8 兼容的堆栈跟踪**:Bun 使用 JavaScriptCore 引擎,但将 `error.stack` 格式化为与 V8 一致的格式,确保依赖 V8 堆栈格式的库(如 Sentry)能正常工作。同时实现了完整的 V8 Stack Trace API:
```javascript
// 捕获自定义堆栈跟踪
function myFunction() {
const err = new Error("custom");
Error.captureStackTrace(err, myFunction);
console.log(err.stack);
}
// 自定义堆栈格式化
Error.prepareStackTrace = (err, callsites) => {
return callsites.map(c => `${c.getFileName()}:${c.getLineNumber()}`).join("\n");
};
```
* **未处理异常的源码预览**:当未捕获的异常或 Promise 拒绝发生时,Bun 会自动打印出错位置的源代码片段,而不是仅显示堆栈文本。这让定位问题更加直观。
* **Sourcemap 自动映射**:Bun 对所有转译文件自动生成和提供 sourcemap。在堆栈跟踪中点击文件路径,可以直接跳转到原始 TypeScript 或 JSX 源码位置,而非转译后的代码。
## Bun.serve 的错误边界
HTTP 服务是 Bun 的核心使用场景,`Bun.serve` 提供了专门的 `error` 回调来处理请求处理过程中的异常:
* **error 回调**:当 `fetch` 函数抛出异常时,Bun 会调用 `error` 回调,该回调应返回一个 `Response` 对象:
```javascript
Bun.serve({
fetch(req) {
throw new Error("Something went wrong");
},
error(error) {
return new Response(`Error: ${error.message}`, {
status: 500,
headers: { "Content-Type": "text/plain" },
});
},
});
```
* **开发模式错误页面**:设置 `development: true` 后,Bun 会在浏览器中展示内置的错误详情页面,包含堆栈跟踪和源码高亮:
```javascript
Bun.serve({
development: true,
fetch(req) {
throw new Error("debug me");
},
});
```
* **Bun.file 流式响应的错误陷阱**:当 `Bun.file()` 作为流式响应的一部分出错时,错误无法通过 `fetch` 内部的 `try/catch` 捕获。这类错误只能由 `error` 回调统一处理:
```javascript
Bun.serve({
fetch(req) {
try {
const file = Bun.file("not-exist.txt");
return new Response(file); // 文件不存在时,try/catch 无法捕获
} catch (e) {
// 这里不会执行
return new Response("Not found", { status: 404 });
}
},
error(error) {
// 必须在这里处理 Bun.file 的错误
return new Response("File not found", { status: 404 });
},
});
```
这是一个容易踩坑的地方:流式响应的错误发生在 `fetch` 返回之后,因此 `try/catch` 无法拦截。
* **动态更新处理器**:使用 `server.reload()` 可以在不停机的情况下更新 `fetch` 和 `error` 处理函数:
```javascript
const server = Bun.serve({
fetch(req) { return new Response("v1"); },
error(err) { return new Response("error v1", { status: 500 }); },
});
// 热更新处理逻辑
server.reload({
fetch(req) { return new Response("v2"); },
error(err) { return new Response("error v2", { status: 500 }); },
});
```
## 生产环境的日志与错误策略
在生产环境中,仅依赖 `console` 输出不足以支撑可观测性需求,需要结合第三方工具:
* **结构化日志**:推荐使用社区库如 Pino(通过 `bun-logger` 等封装)实现 JSON 格式的结构化日志,便于日志平台采集和分析:
```javascript
import pino from "pino";
const logger = pino({ level: process.env.LOG_LEVEL || "info" });
logger.info({ userId: "user123" }, "User login");
```
* **错误监控集成**:Sentry 官方提供 Bun SDK,支持异常捕获和结构化日志:
```javascript
import * as Sentry from "@sentry/bun";
Sentry.init({
dsn: process.env.SENTRY_DSN,
enableLogs: true,
});
// 全局异常捕获
process.on("uncaughtException", (error) => {
Sentry.captureException(error);
});
```
* **日志级别控制**:在 `bunfig.toml` 中可以为 `bun install` 设置日志级别(debug/warn/error),但运行时日志级别需通过环境变量自行管理:
```javascript
const LOG_LEVEL = process.env.LOG_LEVEL || "info";
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
function log(level, message) {
if (levels[level] >= levels[LOG_LEVEL]) {
console.log(`[${level.toUpperCase()}] ${message}`);
}
}
```
* **敏感信息过滤**:错误日志应避免泄露密码、API Key 等敏感字段,在写入日志前做脱敏处理:
```javascript
function sanitize(obj) {
const safe = { ...obj };
for (const key of ["password", "apiKey", "secret"]) {
if (key in safe) safe[key] = "****";
}
return safe;
}
console.error("Request failed:", sanitize(context));
```
Bun 的日志和错误处理机制以标准 `console` API 为基础,通过 `Bun.inspect`、V8 兼容堆栈跟踪、`Bun.serve` 的 `error` 回调等特性提供了更强的调试能力和容错设计。理解 `Bun.file` 流式响应的错误边界、善用 `BUN_CONFIG_VERBOSE_FETCH` 调试网络请求、以及在生产环境集成 Pino 或 Sentry,是实际项目中的关键实践。