Bun 深度解析:被 Anthropic 收购后的疯狂进化,从 Node.js 替代品到 AI 原生运行时的技术全解
写在前面
2025 年 12 月 2 日,Bun 创始人 Jarred Sumner 在官方博客宣布:Bun 被 Anthropic 收购。消息一出,社区炸了。有人担心开源变闭源,有人看好长期稳定性,更多人则在问——一个 JavaScript 运行时,凭什么被 AI 公司看上?
答案藏在过去半年的疯狂迭代里:从 v1.3.0 到 v1.3.13,Bun 在 6 个月内发布了 13 个版本,新增了无头浏览器自动化、内建 cron 调度器、Markdown 终端渲染、并行测试执行器、流式包安装……这不是一个被收购后躺平的项目,而是一台全速运转的引擎。
本文不是一篇 Bun 入门介绍。我们假设你已经知道 Bun 是什么,我们要做的是拆解它——从 Zig 语言底层到 JavaScriptCore 引擎优化,从内建数据库驱动的实现原理到 Anthropic 收购背后的技术逻辑,一层一层剥开看清楚:Bun 到底在下一盘什么棋。
一、为什么是 JavaScriptCore 而不是 V8?
Node.js 用 V8,Deno 用 V8,Bun 偏偏选了 JavaScriptCore(JSC)。这不是随便选的,而是基于一个关键差异:启动策略。
1.1 JIT 编译的两条路
V8 的 JIT 策略是多层的:Sparkplug(基线编译)→ Maglev(中层优化)→ TurboFan(峰值优化)。这个设计追求的是稳态峰值性能——代码跑得越久,优化越激进。代价是启动慢,因为 V8 需要收集类型反馈(Type Feedback)才能触发高层优化。
JSC 的策略不同:LLInt(低级解释器)→ Baseline(基线 JIT)→ DFG(数据流图 JIT)→ FTL(更快Than光速 JIT)。JSC 的 DFG 编译门槛更低,能在更短的热点循环后就开始优化代码。这意味着 JSC 在冷启动场景下天然更快。
Bun v1.3.13 升级 JSC 引擎时合并了 1,316 个上游提交,其中几个关键优化直接印证了这个策略:
- Quick tier-up:函数被证明稳定后更快地升级到 DFG/FTL 编译
- Array.isArray 内在化:在热路径中显著加速
- Promise 解析微优化:新的 PerformPromiseThen DFG/FTL 优化节点
// 实际影响:Serverless 冷启动
// Node.js (V8) 冷启动一个 Express 应用
// real 0m0.342s
// Bun (JSC) 冷启动同样的应用
// real 0m0.087s
// 差距约 4x,这正是 JSC 快速启动策略的直接体现
1.2 内存分配器的战争
Bun v1.3.13 做了一个看似不起眼但影响深远的改动:mimalloc 从 v2 升级到 v3,同时实现了 libpas scavenger 对 Windows 和 Linux 的支持。
mimalloc 是微软研究院开发的通用内存分配器,v3 版本带来了更好的多核扩展性和更小的内存碎片。但 Bun 不只是用了 mimalloc——它还用了 WebKit 的 libpas 作为 JSC 的内存分配器。libpas 的特点是分代式清理(Scavenger),能更积极地回收未使用的内存页。
这两者配合的结果:
Bun v1.3.12: 空闲进程 RSS ≈ 45MB
Bun v1.3.13: 空闲进程 RSS ≈ 42MB(约 5% 降幅)
长时间运行后的内存峰值下降更明显
5% 看起来不多,但在容器化部署中,这意味着你可以在同一个 4GB 节点上多跑 1-2 个实例。
1.3 Source Map 的内存革命
这是一个值得单独讲的技术细节。Bun v1.3.13 完全重写了 Source Map 的内存表示。
旧方案:访问 .stack 时,将 VLQ 映射完整解码到内存列表中。每个映射占约 20 字节。对于 TypeScript 编译器源码(563K 映射),需要 11.3 MB。
新方案:转译时直接写入紧凑的 bit-packed 二进制格式,运行时原地读取,无需全文件解码。每个映射仅需约 2.4 字节,同样的 563K 映射只需 1.29 MB。
表示方式 内存占用 每映射字节数
Mapping.List (v1.3.12) ~11.3 MB 20 B/mapping
LEB128 stream ~2.92 MB 5.4 B/mapping
Bit-packed windows (v1.3.13) ~1.29 MB 2.4 B/mapping
压缩的秘密在于利用转译器输出的结构特征:大多数映射共享同一个 source index,生成的列号和原始列号经常相同。这些重复信息可以用很少的位来编码。
对 bun build --compile 生成的独立可执行文件,Source Map 以零拷贝视图方式加载,大型项目的编译产物体积减少约 1.8 MB。
二、Zig:被低估的系统编程语言
Bun 选择 Zig 而不是 Rust 或 C++,这个决定至今仍有争议。但回头看,Zig 的几个特性确实是 Bun 的关键竞争优势。
2.1 comptime:编译时计算的力量
Zig 的 comptime(编译时执行)让 Bun 能在编译期完成大量工作,减少运行时开销。举个例子,Bun 的 HTTP 服务器路由匹配在编译时就能确定路由表的结构:
// Bun 路由系统的简化 Zig 伪代码
pub fn RouteMatcher(comptime routes: []const Route) type {
return struct {
// 编译时生成匹配函数,避免运行时反射
pub fn match(path: []const u8) ?Route {
// comptime 已展开所有路由的匹配逻辑
inline for (routes) |route| {
if (matchesPattern(route.pattern, path)) {
return route;
}
}
return null;
}
};
}
这意味着路由匹配的代码是编译时展开的,而不是运行时遍历的。对于有 50+ 路由的 API 服务器,这种优化可以显著减少每请求的 CPU 开销。
2.2 与 C 的零开销互操作
JavaScriptCore 是 C++ 写的,Bun 需要直接调用它的 C API。Zig 可以直接 @cImport C 头文件,无需手写绑定:
const c = @cImport({
@cInclude("JavaScriptCore/JavaScript.h");
});
// 直接调用 JSC C API,零开销
pub fn evaluateScript(ctx: c.JSContextRef, script: [*:0]const u8) c.JSValueRef {
const ref = c.JSEvaluateScript(ctx, script, null, null, 1, null);
return ref;
}
如果是 Rust,你需要通过 cxx 或 bindgen 生成交互层,每次调用都有 FFI 开销。Zig 则是直接调用,跟手写 C 代码一样快。
2.3 手动内存管理的精确控制
Zig 没有垃圾回收器,也没有隐式分配。内存分配必须显式传入:
pub fn parseRequest(allocator: std.mem.Allocator, data: []const u8) !Request {
var parser = Parser.init(allocator);
defer parser.deinit(); // 确定性释放
const path = try parser.extractPath(data);
// allocator 贯穿整个调用链,不会出现隐式分配
return Request{
.path = path,
.headers = try parser.extractHeaders(data),
};
}
这对高性能网络服务器至关重要——你永远不用担心 GC 暂停打断请求处理。Bun 的 HTTP 服务器在 10K 并发连接下没有 GC 卡顿,跟这个设计直接相关。
三、内建数据库驱动的技术内幕
Bun v1.3 最大的卖点之一是零依赖数据库支持。但它到底怎么实现的?比 npm 包好在哪里?
3.1 PostgreSQL:二进制协议的直接实现
传统 Node.js 的 pg 包通过 JavaScript 实现了 PostgreSQL 的文本协议和部分二进制协议。Bun 则在 Zig 层面直接实现了 PostgreSQL 的二进制协议,跳过了 JavaScript 层的序列化/反序列化开销。
import { sql } from "bun";
// 标签模板字面量 → 直接生成二进制协议包
// 不经过 JS 字符串拼接,而是编译为 PostgreSQL Bind 消息
const users = await sql`
SELECT id, name, email FROM users
WHERE age >= ${65} AND active = ${true}
`;
标签模板字面量的工作原理:Bun 在解析到 sql 标签时,会将静态部分和动态参数分开。静态 SQL 文本直接作为 Parse 消息发送,动态参数作为二进制 Bind 参数发送——全程不需要 JavaScript 层的字符串拼接和转义,天然防 SQL 注入。
3.2 Redis:比 ioredis 快多少?为什么?
Bun 的 Redis 客户端直接在 Zig 中实现了 RESP3 协议解析器,与 Bun 的事件循环深度集成:
import { redis, RedisClient } from "bun";
// 默认客户端(连接 REDIS_URL 环境变量或 localhost:6379)
await redis.set("user:1001", JSON.stringify({ name: "Alice" }));
const value = await redis.get("user:1001");
// Pub/Sub - 连接复用
const subscriber = new RedisClient("redis://localhost:6379");
const publisher = await subscriber.duplicate();
await subscriber.subscribe("events", (message, channel) => {
console.log(`[${channel}] ${message}`);
});
await publisher.publish("events", "system started");
ioredis 的瓶颈在于:每条命令都要经过 JS→C++→JS 的桥接层,RESP 协议的编码解码在 JavaScript 层完成。Bun 直接在事件循环的 I/O 路径中完成 RESP 编解码,避免了反复的堆分配和 GC 压力。
3.3 统一 SQL API 的设计哲学
import { SQL } from "bun";
// 三种数据库,同一个 API
const pg = new SQL("postgres://localhost/mydb");
const mysql = new SQL("mysql://localhost/mydb");
const sqlite = new SQL("sqlite://data.db");
// 查询语法完全一致
const result = await pg`SELECT * FROM users WHERE id = ${1}`;
const result2 = await mysql`SELECT * FROM users WHERE id = ${1}`;
const result3 = await sqlite`SELECT * FROM users WHERE id = ${1}`;
这不是 ORM,不是查询构建器,而是协议级的统一。Bun 在 Zig 层面对三种数据库的协议差异做了抽象,但保留了参数化查询的安全性。sql.array() 辅助函数处理 PostgreSQL 的数组类型:
import { sql } from "bun";
// PostgreSQL 数组类型支持
await sql`
INSERT INTO users (name, roles)
VALUES (${"Alice"}, ${sql.array(["admin", "user"], "TEXT")})
`;
// 动态列操作 - 批量插入
const newUsers = [
{ name: "Bob", email: "bob@example.com" },
{ name: "Carol", email: "carol@example.com" },
];
for (const user of newUsers) {
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
}
sql(user, "name", "email") 只提取指定字段,忽略对象中的其他属性。这在处理前端提交的表单数据时特别有用——防止用户注入额外字段。
四、全栈开发模式:不是 Vite 替代品,是范式转移
Bun v1.3 的零配置前端开发模式容易被人简化为"又一个 Vite",但它的设计逻辑完全不同。
4.1 从 HTML 出发
Vite 从 package.json 和 vite.config.ts 出发,Bun 从 HTML 出发:
# Bun 的开发起点
bun ./index.html
# 不是这样
npm create vite@latest && cd my-app && npm install && npm run dev
这背后的技术实现:Bun 解析 HTML 中的 <script> 和 <link> 标签,使用原生 Zig 转译器处理 TypeScript/JSX/CSS,然后启动内置开发服务器。整个过程不需要一个配置文件。
<!-- index.html - 这就是你的全部配置 -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script src="./App.tsx"></script>
</body>
</html>
4.2 HMR 的原生实现
文件监听用的是平台原生 API:macOS 用 kqueue,Linux 用 inotify,Windows 用 ReadDirectoryChangesW。不依赖任何 npm 包,不启动额外的 watcher 进程。
React Fast Refresh 的实现也值得关注:Bun 不需要 @vitejs/plugin-react,因为它在转译 JSX 时直接注入了 Fast Refresh 的注册代码。这比 Vite 通过插件链处理更高效——少了至少 3 层抽象。
4.3 全栈路由:前后端一体的真正含义
import { serve, sql } from "bun";
import App from "./index.html";
import Dashboard from "./dashboard.html";
serve({
port: 3000,
development: { hmr: true, console: true },
routes: {
"/*": App, // 前端 SPA
"/dashboard": Dashboard, // 另一个页面
"/api/users": {
GET: async () => Response.json(
await sql`SELECT id, name FROM users LIMIT 10`
),
POST: async (req) => {
const { name, email } = await req.json();
const [user] = await sql`
INSERT INTO users ${sql({ name, email })}
RETURNING *
`;
return Response.json(user, { status: 201 });
},
},
"/api/users/:id": async (req) => {
const { id } = req.params;
const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
if (!user) return new Response("Not Found", { status: 404 });
return Response.json(user);
},
},
});
注意 development.console: true——这会把浏览器控制台的日志回显到终端。前后端运行在同一个进程中,不存在跨域问题,不需要配置 CORS。
4.4 编译为单一可执行文件
# 前后端一起编译
bun build --compile ./index.html --outfile myapp
# 交叉编译到不同平台
bun build --compile --target=bun-linux-x64 ./index.html --outfile myapp-linux
bun build --compile --target=bun-darwin-arm64 ./index.html --outfile myapp-mac
bun build --compile --target=bun-windows-x64 ./index.html --outfile myapp.exe
v1.3.13 在 Linux 上做了重要改进:编译产物不再通过 /proc/self/exe 读取嵌入数据,而是使用 ELF .bun 段。内核在 execve 时通过 PT_LOAD 直接映射数据——零文件 I/O,无需读取权限。
bun build --compile app.ts --outfile myapp
chmod 111 myapp # 只给执行权限,不给读权限
./myapp # 正常运行!
这个特性对容器化部署至关重要:你可以在只读文件系统上运行编译后的 Bun 应用。
五、Bun.WebView:内建无头浏览器的野心
v1.3.12 引入的 Bun.WebView 是一个信号——Bun 不满足于做后端运行时。
5.1 双后端架构
await using view = new Bun.WebView({ width: 800, height: 600 });
await view.navigate("https://bun.sh");
await view.click("a[href='/docs']"); // 等待可操作性,原生点击
await view.scroll(0, 400); // 原生滚轮事件
await view.scrollTo("#install"); // 滚动到元素可见
const title = await view.evaluate("document.title");
const png = await view.screenshot({ format: "jpeg", quality: 90 });
await Bun.write("page.jpg", png);
两个后端:
- WebKit(macOS 默认):使用系统 WKWebView,零外部依赖
- Chrome(跨平台):通过 DevTools Protocol 控制,自动检测已安装的浏览器
关键设计决策:所有输入都作为 OS 级别事件 派发——网站无法区分 view.click() 和真实鼠标点击(isTrusted: true)。选择器方法自动等待可操作性(Playwright 风格):元素必须挂载、可见、稳定、无遮挡。
5.2 与 Playwright/Puppeteer 的本质区别
Playwright 和 Puppeteer 需要下载一个浏览器二进制文件(Chromium),体积通常在 300-400MB。Bun.WebView 的 WebKit 后端直接使用系统自带的 WKWebView,零额外下载。
更重要的是,Bun.WebView 与 Bun 运行时共享同一个进程空间:
// 直接在 WebView 和 Bun 之间共享数据
const cache = new Map();
await using view = new Bun.WebView({
console: true // 捕获页面日志
});
const data = await view.evaluate(`
// 在页面上下文中执行,结果直接返回到 Bun
document.querySelectorAll('.product').length
`);
cache.set('productCount', data);
不需要 CDP WebSocket 桥接,不需要序列化/反序列化的中间层。这对于爬虫、自动化测试、AI Agent 的浏览器交互场景来说,效率提升了不止一个量级。
六、Bun.cron:进程内定时任务调度器
v1.3.12 新增了进程内的 cron 调度器,这解决了长期运行服务中的一个常见痛点。
6.1 进程内 vs OS 级
Bun 提供两种 cron 模式:
// 模式 1:OS 级(持久化,进程退出后仍在运行)
Bun.cron("./cleanup.ts", "0 3 * * *", "Database Cleanup");
// 模式 2:进程内(v1.3.12 新增,与服务器共享状态)
Bun.cron("*/5 * * * *", async () => {
// 直接访问数据库连接池、缓存、模块状态
const staleSessions = await sql`
DELETE FROM sessions WHERE last_active < NOW() - INTERVAL '7 days'
RETURNING id
`;
console.log(`Cleaned ${staleSessions.length} stale sessions`);
});
进程内模式的设计细节很讲究:
- 不重叠:下一次触发必须等上一次的 handler(包括返回的 Promise)完成。慢异步任务不会堆积并发执行
- UTC 时间:
0 9 * * *表示 UTC 9:00,不受系统时区影响(OS 级 cron 则用系统本地时间) - 错误处理与 setTimeout 一致:同步 throw 触发
uncaughtException,Promise reject 触发unhandledRejection --hot安全:所有进程内 cron 在模块图重新评估前被清除,编辑调度、handler 或移除调用都在保存时生效- Disposable:
using job = Bun.cron(...)在作用域退出时自动停止
// 完整的服务器 + cron 示例
import { serve, sql, redis } from "bun";
serve({
port: 3000,
routes: {
"/api/stats": async () => {
const cached = await redis.get("stats:hourly");
if (cached) return Response.json(JSON.parse(cached));
const stats = await sql`SELECT COUNT(*) as count FROM events`;
await redis.set("stats:hourly", JSON.stringify(stats), "EX", 3600);
return Response.json(stats);
},
},
});
// 每小时刷新缓存
Bun.cron("0 * * * *", async () => {
const stats = await sql`SELECT COUNT(*) as count FROM events`;
await redis.set("stats:hourly", JSON.stringify(stats), "EX", 3600);
});
// 每天凌晨 3 点清理过期数据
Bun.cron("0 3 * * *", async () => {
await sql`DELETE FROM sessions WHERE expires_at < NOW()`;
});
七、测试基础设施的重构:从 0 到并行执行
v1.3.13 对 bun test 做了一次彻底的重构,引入了三个新 flag。
7.1 --isolate:文件级隔离
bun test --isolate ./tests
每个测试文件在同一进程内的全新全局环境中运行。文件间 Bun 会:
- 排空微任务队列
- 关闭所有 socket
- 取消所有定时器
- 杀掉子进程
- 创建干净的 global 对象
但关键优化:VM 级转译缓存让共享依赖只解析一次,后续文件直接复用缓存源码,跳过冗余转译。
7.2 --parallel:多进程并行
bun test --parallel # 默认使用所有 CPU 核心
bun test --parallel=8 # 指定 8 个 worker
测试文件被分配到多个 worker 进程。空闲 worker 会从最忙的队列中偷取工作(Work Stealing)。每个 worker 内部自动启用 --isolate。输出保持与串行执行一致——每个测试的 console.log/console.error 被缓冲并原子刷新,不会出现文件间交错。
7.3 --shard=M/N:CI 分片执行
# GitHub Actions 矩阵配置
strategy:
matrix:
shard: [1, 2, 3]
steps:
- run: bun test --shard=${{ matrix.shard }}/3
测试文件按路径排序后 round-robin 分配到各个分片,确保每个分片的文件数差异不超过 1。分片索引从 1 开始。与 --changed 和 --randomize 自然组合。
7.4 --changed:只跑受影响的测试
bun test --changed # 未提交的变更
bun test --changed=HEAD~1 # 最近一次提交的变更
bun test --changed=main # 与 main 分支的差异
bun test --changed --watch # 实时监控
实现原理:构建测试文件的完整导入图,然后过滤出那些传递性依赖于被 git 标记为变更的文件。导入图扫描不进入 node_modules,不需要链接或生成代码,开销极小。
八、包管理器的性能内幕
Bun 的包安装速度一直是宣传重点,但 v1.3.13 做了一个容易被忽略的底层改进:流式解压安装。
8.1 旧方案 vs 新方案
旧方案:下载 .tgz → 全部缓冲到内存 → 解压到 .tar → 全部缓冲到内存 → 提取文件到磁盘。一个 50MB 的包在安装过程中可能占用 150MB+ 内存。
新方案:HTTP 分块到达的同时,边下载边解压边提取。只有正在传输的 HTTP 块加上 libarchive 的固定缓冲区在内存中——完整的归档文件永远不会在内存中完整呈现。
旧方案内存使用: ≈ 压缩包 + 解压包 + 提取缓冲 ≈ 3x 包大小
新方案内存使用: ≈ HTTP 块 + libarchive 缓冲 ≈ 固定开销
// 实际效果
bun install 内存使用下降 17x(对于大型依赖项目)
完整性哈希在压缩字节上增量运行,在提取的文件树提升到缓存之前验证。错误处理和重试逻辑不变。
8.2 Isolated Linker 的性能飞跃
在 peer 依赖繁重的 monorepo 中,v1.3.13 的 isolated linker 改进让安装时间从 20.5 秒降到 2.4 秒——8.5x 加速。
Isolated install(隔离安装)确保每个包只能访问 package.json 中声明的依赖,与 npm/Yarn 的提升式安装(所有依赖平铺在根 node_modules)不同。这解决了 monorepo 中最大的痛点:幽灵依赖(Phantom Dependencies)。
九、Anthropic 收购的技术逻辑
9.1 Claude Code 为什么选 Bun?
Jarred Sumner 在收购公告中透露了一个关键事实:Claude Code 以 Bun 可执行文件的形式分发给数百万用户。如果 Bun 坏了,Claude Code 就坏了。
这不是随便选的。AI 编程助手的分发有几个硬性要求:
- 零依赖:用户不需要先装 Node.js、Python 或任何运行时
- 快速启动:AI Agent 频繁创建和销毁子进程,冷启动时间直接影响响应速度
- 单一二进制:一个文件搞定所有功能,更新、回滚、分发都极简
- 跨平台编译:一套代码编译出 macOS/Windows/Linux 版本
Bun 的 bun build --compile 恰好完美满足了这四点。Claude Code、FactoryAI、OpenCode 都选择了这条路线。
9.2 AI Agent 对运行时的特殊需求
传统 Web 开发对运行时的要求是"稳",AI Agent 对运行时的要求是"快+轻":
| 需求 | 传统 Web 开发 | AI Agent |
|---|---|---|
| 启动速度 | 秒级可接受 | 必须亚秒级 |
| 内存占用 | 几百 MB 正常 | 越小越好,多 Agent 并发 |
| 文件 I/O | 中等 | 大量读写(代码生成、测试执行) |
| 子进程管理 | 偶尔 | 核心(Agent 频繁 spawn) |
| 可执行文件体积 | 不关心 | 越小越好(快速分发) |
Bun v1.3.x 的迭代方向精准对应了这些需求:更快的启动(JSC 优化)、更小的内存(Source Map 重构、mimalloc v3)、更好的子进程管理(--isolate 中自动清理)、更小的编译产物。
9.3 收购后的加速效应
收购前 Bun 团队 14 人,收购后与 Anthropic 的 Claude Code 团队深度协作。效果立竿见影:
- v1.3.8 到 v1.3.13 的发布频率从每月 1 个提高到每 1-2 周 1 个
- GitHub 上 merged PR 最多的贡献者变成了一个 Claude Code bot
- Bug 修复速度显著加快,因为有 Claude Code 团队持续报告和修复
Bun 的月下载量在收购后增长了 25%,突破了 720 万。这个数字还在加速。
十、性能优化实战:从 Node.js 迁移的真实案例
10.1 REST API 服务器
// Node.js + Express + pg 版本
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
app.get('/api/users', async (req, res) => {
const { rows } = await pool.query('SELECT * FROM users LIMIT 10');
res.json(rows);
});
app.listen(3000);
// 峰值: ~19,000 req/s (Express on Node.js 22)
// Bun 原生版本
import { serve, sql } from "bun";
serve({
port: 3000,
routes: {
"/api/users": {
GET: async () => Response.json(
await sql`SELECT * FROM users LIMIT 10`
),
},
},
});
// 峰值: ~59,000 req/s (Bun.serve + Bun.SQL)
// 3.1x 提升,零依赖
10.2 全栈应用编译
# 前后端一起编译为独立可执行文件
bun build --compile ./index.html --outfile myapp
# 编译产物包含:
# - Bun 运行时(JSC + Zig 运行时)
# - 前端打包后的 HTML/JS/CSS
# - 后端 API 路由和数据库逻辑
# - Source Map(bit-packed 格式)
# 体积对比
Node.js + node_modules: ~200MB+
Bun 编译产物: ~80MB (v1.3.13 优化后)
10.3 测试套件并行化
# 大型项目测试(5000+ 测试文件)
# Jest (Node.js)
npx jest --coverage
# 耗时: ~14 分钟
# Bun (串行)
bun test
# 耗时: ~65 秒
# Bun (并行)
bun test --parallel
# 耗时: ~18 秒
# Bun (CI 分片,3 台机器)
bun test --shard=1/3 --parallel
bun test --shard=2/3 --parallel
bun test --shard=3/3 --parallel
# 每台耗时: ~8 秒
十一、与 Deno 和 Node.js 的技术深度对比
11.1 事件循环实现
| 特性 | Node.js | Deno | Bun |
|---|---|---|---|
| 底层 | libuv (C) | Tokio (Rust) | 自研 (Zig) |
| I/O 多路复用 | epoll/kqueue/IOCP | epoll/kqueue/IOCP | epoll/kqueue/IOCP |
| 线程池 | 4 线程默认 | Tokio 线程池 | 按需创建 |
| 文件 I/O | 线程池(异步) | tokio-uring(Linux) | 直接系统调用 |
Bun v1.3.13 的事件循环优化让 async/await 在 v1.3.7 基础上又快了 15%。关键是减少了 Promise 链的中间分配——JSC 的新 PerformPromiseThen 优化节点让 Promise resolve 路径更短。
11.2 TypeScript 支持
Bun 只做转译不做类型检查——这被很多人批评为"半吊子 TypeScript 支持"。但这其实是一个深思熟虑的权衡:
- 编译速度:跳过类型检查让
bun run比ts-node快 30x+ - 开发流程:类型检查交给 IDE 和 CI,运行时只负责执行
- 与 AI 工具的契合:AI Agent 生成的代码在执行前通常已经过了 LLM 的类型校验
# 推荐的开发流程
# 1. 开发时:IDE 实时类型检查
# 2. 运行时:bun run 直接执行(跳过类型检查)
# 3. CI:tsc --noEmit + bun test
# 在 package.json 中
{
"scripts": {
"dev": "bun --watch src/index.ts",
"test": "bun test --parallel",
"typecheck": "tsc --noEmit",
"build": "bun build --compile ./src/index.ts --outfile dist/app"
}
}
11.3 安全模型
这是 Bun 的短板,也是 Deno 的优势。Deno 默认沙箱化,需要显式授权文件/网络/环境变量访问。Bun 和 Node.js 一样无限制。
但 Bun v1.3 引入了几个安全相关的改进:
# bunfig.toml - 安全扫描器
[install.security]
scanner = "@socketsecurity/bun-security-scanner"
# 最小发布年龄:防止供应链攻击
[install]
minimumReleaseAge = 604800 # 7 天,单位秒
这不是权限模型,而是供应链安全措施。真正的沙箱化,Bun 目前还没有时间表。
十二、实战:构建一个 AI Agent 工具链
结合 Bun 的新特性,构建一个完整的 AI Agent 工具链:
// agent.ts - AI Agent 工具链
import { serve, sql, redis } from "bun";
// 任务存储
interface Task {
id: number;
prompt: string;
status: "pending" | "running" | "done" | "failed";
result?: string;
created_at: Date;
}
// Web 界面
import AgentUI from "./agent.html";
serve({
port: 3000,
development: { hmr: true },
routes: {
"/*": AgentUI,
"/api/tasks": {
GET: async () => {
const tasks = await sql`
SELECT * FROM tasks ORDER BY created_at DESC LIMIT 50
`;
return Response.json(tasks);
},
POST: async (req) => {
const { prompt } = await req.json();
const [task] = await sql`
INSERT INTO tasks ${sql({ prompt, status: "pending" })}
RETURNING *
`;
await redis.publish("task:new", JSON.stringify(task));
return Response.json(task, { status: 201 });
},
},
"/api/tasks/:id": async (req) => {
const { id } = req.params;
// 先查缓存
const cached = await redis.get(`task:${id}`);
if (cached) return Response.json(JSON.parse(cached));
const [task] = await sql`SELECT * FROM tasks WHERE id = ${id}`;
if (!task) return new Response("Not Found", { status: 404 });
await redis.set(`task:${id}`, JSON.stringify(task), "EX", 300);
return Response.json(task);
},
},
});
// 定时清理过期任务
Bun.cron("0 */6 * * *", async () => {
await sql`
DELETE FROM tasks
WHERE status = 'done' AND created_at < NOW() - INTERVAL '7 days'
`;
console.log("[cron] 清理了过期任务");
});
// 定时检查卡住的任务
Bun.cron("*/5 * * * *", async () => {
const stuck = await sql`
UPDATE tasks SET status = 'failed'
WHERE status = 'running'
AND created_at < NOW() - INTERVAL '30 minutes'
RETURNING id
`;
if (stuck.length > 0) {
console.log(`[cron] 标记 ${stuck.length} 个卡住的任务为失败`);
}
});
console.log("Agent 工具链运行在 http://localhost:3000");
编译为独立可执行文件:
bun build --compile ./agent.ts --outfile agent-cli
# 分发:一个文件,零依赖,跨平台
chmod +x agent-cli
./agent-cli
十三、局限与风险:不吹不黑
13.1 Node.js 兼容性约 90%+
Bun 通过了大部分 Node.js 测试用例,但仍有缺失模块:
node:repl:未实现node:trace_events:未实现node:cluster:部分功能缺失node:worker_threads:高级功能仍有问题- N-API 原生模块(C++ 插件):部分无法正常工作
实际影响:如果你用的是纯 JavaScript 的 npm 包,大概率没问题。如果依赖了编译型原生模块(如 bcrypt、canvas、sharp),可能需要测试。
13.2 没有 LTS
Bun 采用快速迭代策略,没有长期支持版本。企业级项目通常需要稳定的运行时版本和长期安全补丁。对于要求 99.99% 可用性的生产环境,这是个真实风险。
不过 Anthropic 收购后,长期稳定性有了更强的背书。Jarred Sumner 明确说过:"Claude Code 以 Bun 可执行文件分发——如果 Bun 坏了,Claude Code 就坏了。"这给了 Anthropic 直接动力保持 Bun 的质量。
13.3 Windows 原生支持仍不完善
虽然 v1.3.10 加入了 Windows ARM64 支持,但 Windows 上仍有不少已知问题。主要在 Windows 上开发的团队需要评估。
13.4 TypeScript 类型检查的缺失
Bun 只做转译不做类型检查。这意味着你可能在运行时遇到类型相关的 bug,而 IDE 和 CI 中通过 tsc --noEmit 可以提前发现。这不是 bug,是设计选择,但需要团队建立对应的开发流程。
十四、未来展望:Bun 的下一步
从 v1.3.x 的迭代节奏和 Anthropic 的投入方向看,Bun 的下一个阶段可能在以下方向突破:
更完善的无头浏览器自动化:
Bun.WebView目前只支持基础操作,未来可能加入网络拦截、请求 mock、性能分析等高级功能,直接对标 Playwright安全沙箱:供应链攻击越来越频繁,Bun 可能会引入 Deno 式的权限模型,至少对第三方脚本执行施加限制
AI Agent 专用 API:既然 Anthropic 的核心业务是 AI,Bun 很可能推出针对 Agent 工作流优化的 API——比如更快的子进程创建、更高效的代码沙箱执行、与 LLM 的原生集成
WebAssembly 组件模型支持:JSC 已经有了 WASM SIMD 优化,下一步可能是 WASI 和组件模型,让 Bun 成为多语言运行时
商业化托管服务:虽然 Jarred 说"Bun 当前零收入"并选择了 Anthropic 而非自己做云,但长期来看,Bun Deploy 或类似的边缘计算服务是自然延伸
结语
Bun 的故事不是"又一个 JavaScript 运行时"的故事。它是一个关于工程哲学选择的故事:用 Zig 而非 Rust,用 JSC 而非 V8,用 All-in-One 而非模块化——每一个选择都有代价,但合在一起形成了一个独特的价值主张。
被 Anthropic 收购不是终点,而是新的起点。AI 编程工具正在重新定义开发者的工作流,而运行时是这个工作流的基础设施层。Bun 在这个位置上的演进速度,可能比我们所有人预想的都快。
如果你还没试过 Bun,现在是最好的时机。不需要全面迁移,从 bun install 替代 npm 开始,或者用 bun test 替代 Jest——感受一下速度的差异,然后决定要不要走得更远。
参考资源: