跳到正文

目录

Bun v1.3.14:93.4K+ Stars 的 all-in-one JavaScript 工具链完整指南

Bun v1.3.14:93.4K+ Stars 的 all-in-one JavaScript 工具链完整指南

Bun 把运行时、打包器、测试运行器、包管理器四件事压进同一个二进制。这套合并带来的工程含义,不止是「启动快 4 倍」这么简单:它改变了 JS 工具链的依赖管理方式——以前一个项目要 node + esbuild + jest + npm 四个独立工具,升级节奏对不齐、报错栈跨工具、锁版本要分别管,现在只有一个版本号要追。换来的代价是 Node.js 兼容性不是 100%,部分原生模块仍要回退到 Node。

下面从引擎选型、四合一架构、执行路径、迁移判断四个层面展开。版本基线 v1.3.14(2026-05-13),约 93.4K Stars,已越过实验阶段,进入生产可用区间。生产可用不等于零风险——文末给出具体的迁移边界。


快速信息卡

项目信息
Stars约 93.4K(截至 2026-05)
Forks约 4.7K
许可证MIT
语言Zig(核心运行时)
仓库oven-sh/bun

总览:Bun 把什么压进了一个二进制

Bun 对自己的定义是「all-in-one toolkit for JavaScript and TypeScript apps」,拆开是四个职责。Node.js 生态里这四件事通常由四个独立工具承担,Bun 把它们合并到一个二进制里:

职责替代对象关键差异
运行时Node.js引擎换成 JavaScriptCore,冷启动和内存占用更低
打包器esbuild / Webpack / Vite内置在二进制里,不需要再装一个 dev 依赖
测试运行器Jest / VitestAPI 与 Jest 高度相似,迁移成本低
包管理器npm / yarn / pnpm全局缓存 + 硬链接,安装路径与 pnpm 思路接近

以前一个项目要 node + esbuild + jest + npm,现在只需要一个 bun。少装几个工具是直观收益,但版本对齐和配置共享改变了开发体验——bun build 出来的产物和 bun run 跑的代码用同一个 transpiler,bun testbun run 共享同一份模块解析逻辑。Node.js 生态里,esbuild 升级破坏 Jest snapshot、ts-node 和 Vite 的 TypeScript 配置不一致这类问题,在 Bun 里不太会出现,因为四个工具共用同一套底层实现。

合并也有代价。Node.js 生态里 esbuild 出问题,可以换 swc 或 Vite;Bun 的打包器出问题,只能等官方修。这是单二进制架构的固有 trade-off:工具出问题不能换,但四个工具的行为永远对齐。

与 Node.js / Deno 的对照

Node.jsDenoBun
引擎V8V8JavaScriptCore
实现语言C/C++RustZig
包管理npm(独立工具)内置(去中心化 URL)内置(npm registry 兼容)
TypeScript需 tsc / ts-node / swc原生支持原生支持(内置 transpiler)
打包器需单独安装无内置打包器内置
测试运行器需单独安装内置内置
Node.js 兼容原生通过 npm: 协议兼容通过 node: 模块兼容层

三者的根本分歧在于如何对待 npm 生态。Node.js 就是 npm 生态本身;Deno 早期拒绝 npm,后来通过 npm: 协议兼容;Bun 一开始就做 npm registry 兼容,把 package.jsonnode_modules 当成事实标准保留下来。这个选择决定了 Bun 能直接跑大部分现有 Node.js 项目,而 Deno 的迁移成本更高——Deno 的迁移要改 import 路径,Bun 的迁移只需要换运行时。


为什么是 JavaScriptCore,以及代价是什么

Bun 与 Deno 的技术分歧在引擎选型。Deno 选 V8,原因是 V8 最成熟、性能上限最高,且 Deno 团队有 V8 经验。Bun 选 JavaScriptCore(WebKit 的 JS 引擎),目标有两个:冷启动和内存占用。

冷启动开销低。JavaScriptCore 的初始化路径比 V8 短。V8 启动时要构建完整的 isolate、初始化 JIT 编译器、加载内置库,这套开销在长期运行的服务端进程里被摊薄,但在 CLI 工具、Serverless 函数、脚本这类「跑一次就退出」的场景里占比很高。Bun 的目标场景里冷启动是常态,JSC 的这个特性刚好对上——Serverless 函数的冷启动延迟直接进用户感知的 P99,CLI 工具的启动延迟则卡在开发者每一次保存-运行的循环里。

内存占用更紧凑。JSC 的内存模型对短生命周期进程更友好。在官方和社区的基准里,同样跑一个 Hello World HTTP 服务,Bun 的常驻内存通常比 Node.js 低一到三成上下,具体数字随进程负载和环境摆动。在容器化部署、函数计算场景里,这直接影响实例密度——同样的内存预算下,能跑更多 Bun 实例,单位成本更低。

Zig 在这个位置的作用。JavaScriptCore 的嵌入接口是 C API,正好和 Zig 手写的 FFI 配合;而 V8 把嵌入层做成了 C++ 抽象,这也是 Bun 不选 V8 的工程原因之一——用 Zig 去绑定 V8 的 C++ 类要平白多一层 glue。Zig 的手动内存管理和 comptime 特性让 Bun 能在编译期做更多检查,运行时开销更低。这是 Bun 团队的技术判断,行业里没有共识——Deno 团队认为 V8 + Rust 的组合更稳,因为 V8 的成熟度和 Rust 的内存安全各有保障。

代价是兼容性。Node.js 生态里有一批包直接调用了 V8 的内部 API(典型的有 node-bindings、部分 native addon、用了 v8.h 的包),这些在 Bun 上跑不起来。Bun 通过 node: 模块兼容层覆盖了 fspathprocessBuffer 等常用模块,但涉及 V8 内部接口的包需要 polyfill 或替代方案。具体的兼容性状态可以查 Bun 的 Node.js 兼容性列表——迁移前先跑一遍现有测试套件,比看文档更可靠。

实现语言的时效说明:本文写作于 2026-05,基线锁定 v1.3.14,此时运行时核心仍是 Zig。2026-07 官方发布《Rewriting Bun in Rust》,将核心从 Zig 迁至 Rust,并随 2026-08 的 v1.4 正式发布;官方称这次迁移以内存安全为目标,保持行为一致。若你读到本文时已经用上 v1.4+,文中有关 Zig 的实现细节应以新版本为准。


一次 bun run 怎么流过系统

假设入口是 index.tsx,这条路径展示了 Bun 的四个组件如何协作:

// index.tsx
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello from Bun!'))

export default { port: 3000, fetch: app.fetch }

执行 bun run index.tsx,路径如下:

1. 入口解析。Bun 读取 index.tsx,识别出是 TypeScript + JSX。这里不调用 tsc,也不读 tsconfig.json 做类型检查——Bun 内置的 transpiler 只做语法转换(TS → JS、JSX → createElement 调用),不做类型诊断。类型检查交给 IDE 或独立的 tsc --noEmit。Bun 启动快,跳过了类型检查这一步是一个原因。

2. 模块图构建。Bun 从 index.tsx 出发,递归解析 import 语句。遇到 hono,按 Node.js 模块解析算法查找 node_modules/hono,读到其 package.jsonexports 字段,定位入口文件。整个模块图在内存里构建完成,每个模块记录自己的路径、依赖、转换后的代码。

3. JavaScriptCore 接管。模块图构建完成后,Bun 把转换后的代码喂给 JavaScriptCore。JSC 解析、编译(先用解释器 tier,热点代码再 JIT)、执行。Honoapp.fetch 被注册为 HTTP 请求处理函数。

4. 内置 API 接入Bun.serve(由 export default { fetch } 触发的默认行为)底层走的是 Bun 用 Zig 实现的 HTTP 服务器,在 Linux 上用 io_uring,在 macOS 上用 kqueue。请求进来后不经过 libuv 这层,直接从内核事件循环到 JSC 回调。

5. 进程退出。HTTP 服务器保持运行,进程不退出。如果是脚本类入口(没有起服务),JSC 执行完顶层代码后 Bun 进程直接退出。

这条路径里和 Node.js 的差异在第 1、2 步:Node.js 跑 TypeScript 要么靠 ts-node(运行时 transpile,慢),要么靠 tsc 预编译(多一步构建),要么靠 --loader 钩子(生态碎片化)。Bun 把 transpiler 直接编进二进制,启动时一次性完成模块图构建和语法转换,没有跨进程通信开销。


四个场景的工程用法

运行时:直接跑 TypeScript 和 JSX

node index.js 在 Bun 里写成 bun run index.tsx,TypeScript 和 JSX 开箱即用,不需要 tsconfig.json、不需要 ts-node、不需要 --loader

# 运行 TypeScript 文件
bun run index.tsx

# 运行 package.json 中的脚本
bun run start

# REPL
bun

内置 Web API 覆盖 fetchWebSocketStreamsCrypto,以及 Bun 特有的 Bun.sqlBun.redisBun.serveBun.filenode: 模块兼容层覆盖了 fspathprocessBufferevents 等常用模块,但仍有少量缺失——遇到不兼容的包,先查兼容性列表,再决定是 polyfill 还是回退 Node。

Bun 的 transpiler 不做类型检查。bun run 会跑过有类型错误的代码,只要语法能转换。这是有意的工程取舍:类型检查是静态分析,transpile 是语法转换,两件事分开做能让运行时路径更短。代价是类型错误不会在运行时暴露,生产构建前要单独跑 tsc --noEmit 或在 CI 里加类型检查步骤。一个可行的配置是在 package.jsonprebuild 脚本里挂 tsc --noEmit,让构建前自动检查一次。

打包器:Bun.build 与单文件可执行文件

Bun 的打包速度对标 esbuild(官方标称 1.75x),支持插件系统、代码分割、Tree-shaking、Minifier。配置走 Bun.build() API(官方稳定 API),写在 build.ts 里用 bun run 执行:

// build.ts —— 用 Bun.build API 配置打包
const result = await Bun.build({
  entrypoints: ["./src/index.tsx"],
  outdir: "./dist",
  minify: true,
  target: "browser",
});

if (!result.success) {
  for (const log of result.logs) {
    console.error(log);
  }
  process.exit(1);
}

console.log(`Build OK: ${result.outputs.length} files`);
bun run build.ts

命令行等价写法(适合简单场景,不需要可编程配置时):

bun build --entrypoints ./src/index.tsx --outdir ./dist --minify

注意:Bun 官方目前主推 bunfig.toml 配置运行时行为、Bun.build() API 配置打包。社区早期流传的 bun.config.ts + @bun/runtimedefineConfig 写法不是官方稳定 API,不要照搬。

Bun.build 最具差异化的能力是 --compile,把 JS 代码和 Bun 运行时一起打成一个独立的可执行文件:

bun build --compile --entrypoints ./src/cli.ts --outfile mycli

产物是一个不依赖系统 Node.js / Bun 的二进制,适合分发 CLI 工具。这件事在 Node.js 生态里要靠 pkgnexe,且这两个工具维护活跃度已明显下降。

注意 --compile 出来的二进制体积通常在 50-90 MB 量级(包含整个 Bun 运行时),不适合对体积敏感的分发场景。如果目标平台是资源受限的嵌入式设备或要求秒级下载的 CLI 分发,还是要回到 pkg + Node.js 的精简方案,或者用 deno compile 对比体积。

Bun.build 和 esbuild / Vite 的选择取决于「项目其他工具是否也在 Bun 生态内」。如果运行时和测试都已经切到 Bun,打包器一起切能消除一份配置和一份依赖——bun.config.tsbunfig.toml 共享同一份解析逻辑,构建产物和运行时行为一致。如果项目还在 Node.js 上跑、只是想试 Bun 的打包速度,esbuild / Vite 的生态成熟度(插件、文档、社区案例)仍是更稳的选择,Bun.build 的插件 API 相对年轻,复杂场景的踩坑成本更高。

测试运行器:Jest / Vitest 用户零配置迁移

// sum.test.ts
import { describe, test, expect } from "bun:test";

function sum(a: number, b: number) {
  return a + b;
}

describe("math", () => {
  test("adds two numbers", () => {
    expect(sum(1, 2)).toBe(3);
  });
});
bun test

bun:test 的 API 与 Jest 相似:describetestexpectbeforeEachafterEach、Mock、Snapshot 都覆盖了。从 Jest 迁移主要改 import 路径(jestbun:test)和配置文件(jest.config.jsbunfig.toml)。Vitest 用户迁移成本更低,因为 Vitest 的 API 本来就借鉴 Jest。

迁移时有三类场景会失败:第一类是 jest.mock() 的 module factory 行为差异——Bun 对 ESM/CJS 混合模块的 mock 注入路径和 Jest 不完全一致,依赖 jest.mock("node:fs", ...) 这类核心模块 mock 的测试要逐个验证;第二类是 jest.useFakeTimers() 的实现细节——Bun 的 fake timer 不支持 Jest 的 "modern" / "legacy" 切换,依赖特定行为的测试要重写;第三类是 Snapshot 序列化——Bun 的 Snapshot 格式与 Jest 不完全兼容,迁移时第一次运行会重新生成所有 Snapshot,要人工 review 一次确保格式没漂移。

DOM 测试通过 bun:test 的 jsdom 兼容层支持,但这个层不如 Vitest 的 happy-dom 集成成熟,复杂组件测试可能踩坑。如果项目里有大量 React Testing Library 测试,建议先在 Vitest 上跑稳,再评估是否切到 Bun——前端组件测试对 DOM 模拟的依赖度很高,Bun 在这块的兼容性还在迭代。

包管理器:npm 兼容,安装路径接近 pnpm

bun install          # 等价于 npm install
bun add <pkg>        # 等价于 npm install <pkg>
bun add -d <pkg>     # 等价于 npm install -D <pkg>
bun remove <pkg>     # 等价于 npm uninstall <pkg>
bunx cowsay 'Hello!' # 等价于 npx cowsay
bun upgrade          # 升级 bun 自身

Bun 的包管理器兼容 npm registry,可以直接读 package.jsonpackage-lock.json(会生成自己的 bun.lockb 二进制锁文件)。安装速度比 npm 快,原因是全局缓存 + 硬链接:包在全局缓存里只存一份,每个项目的 node_modules 通过硬链接指过去,不重复占磁盘。这个思路和 pnpm 一致,区别在于 Bun 用 Zig 重写了下载、解压、链接的全流程,没有 Node.js 进程启动开销。

一个迁移注意点:bun.lockb 是二进制格式,不能像 package-lock.json 那样直接读 diff。要查依赖变化用 bun install --dry-runbun pm why <pkg>。如果团队里有人用 npm 有人用 Bun,锁文件冲突会是个问题——建议统一工具链。

Bun install 和 pnpm 的取舍取决于团队对锁文件格式和 workspace 特性的需求。两者都用全局缓存 + 硬链接,性能差异不大。pnpm 的 pnpm-workspace.yaml 在 monorepo 场景里更成熟,支持 catalogsoverrides 这类高级依赖管理特性;Bun 的 workspace 支持覆盖了基本场景,但复杂依赖拓扑下的边界行为还在收敛。如果 monorepo 里有跨包依赖覆盖、版本 catalog 这类需求,pnpm 仍是更稳的选择;如果是单包或简单 monorepo,Bun install 的安装速度优势更明显。


v1.3.14 改了什么(2026-05-13)

v1.3.14 是 2026 年 5 月中旬的稳定版本。这个版本不是兼容性收尾,而是把 Bun 从「更快的 Node.js」往「自带基础设施的运行时」推了一大步。主要更新集中在图像处理、安装链路和 HTTP 现代协议三个方面:

  • Bun.Image:内置图像处理。这是本版本最大的新能力,取代了 Node.js 生态里必须靠 sharp(原生 C++ 模块,装一次就要 node-gyp 编译环境)才能做的活。Bun.Image 直接内置 JPEG、PNG、WebP、GIF、BMP 的静态编解码;HEIC、AVIF、TIFF 在 macOS / Windows 上走系统后端。官方 benchmark(对比 sharp 0.34.5):读取元数据 metadata() 快约 70 倍,常见 resize 快 1.2-1.4 倍。API 是链式的,.resize().rotate().webp() 一路接下去;接收路径、ArrayBufferBlobBun.file 作为输入,用 .bytes() / .buffer() / .write(dest) 落盘,还能把实例直接当 Response body 返回、由运行时自动设 Content-Type。省掉 sharp 意味着 CI 不再为了一个缩略图场景去装 libvips,也不用担心架构不匹配导致的原生二进制报错。

  • 全局虚拟存储(Global Virtual Store)bun install 的 isolated linker 新增 install.globalStore = true,把每个包只在全局缓存中实例化一次,项目 node_modules 里只放指向它的 symlink。官方对约 1,400 个包的前端项目的预热安装测试:优化前约 841 ms、clonefileat 调用约 1,387 次,开启后降到约 115 ms、0 次——快了约 7 倍。热点在 CI 的反复重建路径。注意这是实验特性,默认关闭,且只有来自不可变缓存源、无受信生命周期脚本的包才有资格进全局存储。

  • HTTP/3(QUIC)支持Bun.serve 加一个 http3: true 标志就能同时监听 TCP(HTTP/1.1+2)和 UDP(HTTP/3),现有 fetch 处理函数三种协议通吃,官方标称静态路由吞吐约 509k req/s,约为同环境标准 HTTPS 的两倍。同为实验特性,官方明确警示不要在生产部署。

  • fetch() 的 HTTP/2 / HTTP/3 客户端(实验)。新增 { protocol: "http2" } / { protocol: "http3" } 选项,同一 origin 的并发请求可共享一条多路复用连接;HTTP/3 客户端还能根据 Alt-Svc 头自动把后续请求升级到 QUIC。

  • 重写的 fs.watch 后端。Linux / macOS / FreeBSD 上改为直接对接 inotify、FSEvents、kqueue,修了递归监听漏掉新增目录、文件删除重建后不再触发 change 等问题。

  • --no-orphans。父进程死掉(哪怕被 SIGKILL)时 Bun 自动退出,并递归终止自己派生的所有子进程,适合被 Electron、CI runner 这类 supervisor 拉起、中途强杀的场景。

完整 Release Notes


性能数字:测的是什么,不能推出什么

先澄清:下面列的是社区常见的代表性数量级,具体数字随参数、机型和负载变化很大,不建议当成精确结论。这段的重点不是数字本身,而是它们各自在测什么、不能推出什么:

操作Node.jsBun倍数
HTTP Hello World (req/s)~25k~85k3.4x
npm install (cold)15s2s7.5x
TypeScript 编译 (cold)8s0.8s10x
bun test (Jest 项目迁移)12s1.5s8x

这些数字各自测的是不同的东西,不能笼统说「Bun 比 Node.js 快 N 倍」:

  • HTTP Hello World 测的是「最小请求的吞吐上限」,反映的是 HTTP 栈和事件循环的基线开销。真实业务请求带数据库、缓存、序列化,瓶颈不在 HTTP 栈,这个倍数会收窄很多。
  • npm install cold 测的是「冷缓存下的安装耗时」,反映的是下载、解压、链接的全流程。Bun 快的原因是 Zig 实现没有 Node.js 进程启动开销 + 全局缓存命中率高。热缓存下差距会缩小。
  • TypeScript 编译 cold 测的是「启动到首字节输出」,反映的是 transpiler 启动开销。Bun 不做类型检查,tsc 做——这是两个不同的工作,倍数反映的是「跳过类型检查能省多少时间」,而不是「Bun 的 transpiler 比 tsc 快 10 倍」。
  • bun test vs Jest 测的是「测试发现 + 执行 + 报告」全流程。Bun 快的原因是测试运行器和运行时共用进程,没有 Jest 的 worker 进程启动开销。

真实项目里的体感差距通常比这些数字小。但「npm install 从 15 秒到 2 秒」这种在 CI 上每天跑几十次的操作,体感差距是实实在在的。

这些数字不能直接推出几件事,迁移决策时要记住:第一,不能从「HTTP Hello World 3.4x」推出「业务接口 3.4x」——业务接口的瓶颈在数据库、缓存、序列化,HTTP 栈占比通常不到 10%;第二,不能从「TypeScript 编译 10x」推出「Bun 的 transpiler 比 tsc 快 10x」——前者跳过了类型检查,后者做了完整类型诊断,测的不是同一件事;第三,不能从「bun test 8x」推出「测试套件整体快 8x」——如果测试里有大量 IO(数据库、网络、文件),IO 等待时间不变,整体提速会被稀释。判断自己的项目能拿到多少,可以在分支上跑一次 bun testbun run,对比 CI 耗时——比看 benchmark 更准。


什么时候该用 Bun,什么时候该再等等

适合用 Bun 的场景

  • 新项目启动:不需要在 node + esbuild + jest + npm 之间来回配置,一个二进制搞定。
  • 对启动速度敏感:CLI 工具、Serverless 函数、脚本类场景,冷启动开销直接进 P99。
  • TypeScript 优先项目:零配置 TypeScript 支持,省掉 ts-node / swc / vite 的选型决策。
  • 需要单文件分发bun build --compile 输出独立可执行文件,适合 CLI 工具分发。
  • Monorepo:Bun 的 workspace 支持与 yarn / pnpm 一致,安装速度更快,bun test 跨 workspace 跑测试也比 Jest + Nx 配置简单。

仍建议用 Node.js 的场景

  • 强依赖 Node.js 原生模块:某些 npm 包内部调用了 V8 特定 API(如 node-bindings、部分 native addon、用了 v8.h 的包),在 Bun 上跑不起来。迁移前先用 bun run 跑一遍现有测试套件,看哪些包报错。
  • 需要 Node.js 生态里的企业级中间件:部分微服务框架、APM agent、Service Mesh sidecar 的 Node.js 集成在 Bun 上的验证还不充分。
  • Windows arm64 生产环境:Bun 在这块的稳定版支持相对较新,生产环境建议再观察一两个版本。
  • 强依赖 Jest 生态jest-styled-componentsjest-image-snapshot 这类深度集成 Jest 的包,迁移到 bun:test 可能要改 mock 注入方式。

两个列表列了多个场景,但判断变量只有一个:项目对 Node.js 生态的耦合度有多深。耦合度低(新项目、标准 API、少量原生依赖)→ Bun 收益直接;耦合度高(企业中间件、native addon、深度 Jest 集成)→ 迁移成本会吃掉速度收益。中间地带的项目,按下面的排查指引做一次试运行,比看文档判断更准。

迁移排查指引

从 Node.js 迁移到 Bun,按这个顺序排查:

  1. bun install 能否装上依赖。部分依赖 postinstall 脚本的包(如 esbuildsharp)在 Bun 上可能行为不同。报错先看 bun install --verbose
  2. bun test 能否跑通现有测试。Jest 项目直接 bun test 通常能跑,但 mock 行为、timer mock、module mock 的实现细节有差异,遇到不通过的测试逐个排查。
  3. bun run 能否启动服务。HTTP 服务、定时任务、队列消费者都跑一遍,观察 node: 模块兼容层的报错。
  4. 生产环境灰度。先在非核心服务上跑,观察内存、CPU、错误率。Bun 的内存模型和 Node.js 不同,常驻内存下降是正常的,但 GC 行为差异可能导致长跑服务的内存增长曲线不一样。

快速安装

# Linux/macOS(推荐)
curl -fsSL https://bun.sh/install | bash

# Windows
powershell -c "irm bun.sh/install.ps1|iex"

# npm 安装(跨平台)
npm install -g bun

# Homebrew
brew tap oven-sh/bun
brew install bun

# 升级
bun upgrade

常见问题

Bun 能完全替代 Node.js 吗? 不能。Bun 覆盖了 Node.js 的大部分常用 API 和主流包,但涉及 V8 内部接口的 native addon、部分企业级中间件、某些边缘 node: 模块仍不兼容。一个判断方法是:如果项目依赖列表里没有 node-gyp 构建的包、没有用 v8.h 的包、没有深度集成 APM agent,Bun 大概率能跑通;只要踩到这三类之一,就要评估替代方案或回退 Node。生产迁移前必须跑一遍现有测试套件。

Bun 的 TypeScript 支持和 tsc 一样吗? 不一样。Bun 只做语法转换,不做类型检查。bun run 会跑过有类型错误的代码。类型检查要单独跑 tsc --noEmit 或在 IDE 里做。这个差异源于职责不同——tsc 的类型检查是静态分析工具,Bun 的 transpiler 是运行时组件。把类型检查放在 CI 或 pre-commit hook 里,比让运行时承担类型诊断更合理。

bun.lockb 为什么是二进制? 为了解析速度。二进制格式读取比 JSON 快,但不可读。查依赖变化用 bun install --dry-run(预览将要发生的变化)或 bun pm ls / bun pm tree(查看依赖树)。如果团队在 code review 里要看依赖 diff,一个折中是同时维护 npm 的 package-lock.json(先 npm install --package-lock-only 生成一次)做对照,或把 bun.lockb 的变化量限制在每次 upgrade 时单独 review。

Bun 和 Deno 该选哪个? 看你对 npm 生态的依赖程度。重度依赖现有 npm 包选 Bun(兼容性更好);从零开始、想要更干净的权限模型和 TypeScript 优先体验选 Deno。两者都在向对方靠拢——Deno 加了 npm: 兼容,Bun 在加强权限模型。具体判断:如果项目要跑在 Cloudflare Workers / Deno Deploy 这类边缘运行时上,Deno 的权限模型和部署体验更顺;如果要跑在传统 VPS / 容器 / Serverless 函数上,Bun 的 Node.js 兼容性让迁移路径更短。

Bun 生产环境稳定吗? v1.3.x 已经被多家公司用于生产(包括 Vercel 部分内部工具、Clerk 等),但稳定不意味着没有 bug。核心路径服务建议先灰度,观察 1-2 个版本周期再全量切换。灰度时要重点观察三个指标:长跑服务的内存增长曲线(Bun 的 GC 行为和 Node.js 不同)、HTTP 服务的错误率(特别是用了 Bun.serve 的流式响应)、node: 模块兼容层的边缘 case(某些 API 行为可能和 Node.js 有细微差异)。


采用顺序建议

  1. 个人工具和脚本:直接用。CLI 工具、自动化脚本、本地开发环境,Bun 的零配置和启动速度收益最大,风险最低。
  2. 新项目后端:可以用。从零开始没有迁移成本,Bun.serve + Bun.sql 的组合适合中小型 API 服务。
  3. 现有项目灰度:先跑测试套件,再灰度非核心服务。观察 1-2 个版本周期。
  4. 企业核心系统:再等等。等 Node.js 兼容性进一步收敛、APM / Service Mesh 集成更成熟后再评估。

落地时按这个清单逐项打勾:

  • 在一个非核心脚本上跑通 bun run,确认 transpiler 行为符合预期
  • 现有测试套件用 bun test 跑一遍,记录失败的测试和原因
  • bun install 装一遍依赖,对比 package-lock.json 的依赖树差异
  • 选一个非核心服务灰度,监控内存、CPU、错误率 1-2 个版本周期
  • package.jsonprebuild 里挂 tsc --noEmit,补上类型检查
  • 评估 node: 模块兼容层覆盖范围,列出需要 polyfill 或回退的包

官网:https://bun.com 文档:https://bun.com/docs GitHub:https://github.com/oven-sh/bun(93.4K+ ⭐)

钳岳星君整理 | 2026 年 5 月 16 日

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。