编程 Bun 深度解析:被 Anthropic 收购后的疯狂进化,从 Node.js 替代品到 AI 原生运行时的技术全解

2026-04-30 23:25:26 +0800 CST views 18

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,你需要通过 cxxbindgen 生成交互层,每次调用都有 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.jsonvite.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 或移除调用都在保存时生效
  • Disposableusing 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 编程助手的分发有几个硬性要求:

  1. 零依赖:用户不需要先装 Node.js、Python 或任何运行时
  2. 快速启动:AI Agent 频繁创建和销毁子进程,冷启动时间直接影响响应速度
  3. 单一二进制:一个文件搞定所有功能,更新、回滚、分发都极简
  4. 跨平台编译:一套代码编译出 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.jsDenoBun
底层libuv (C)Tokio (Rust)自研 (Zig)
I/O 多路复用epoll/kqueue/IOCPepoll/kqueue/IOCPepoll/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 runts-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 包,大概率没问题。如果依赖了编译型原生模块(如 bcryptcanvassharp),可能需要测试。

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 的下一个阶段可能在以下方向突破:

  1. 更完善的无头浏览器自动化Bun.WebView 目前只支持基础操作,未来可能加入网络拦截、请求 mock、性能分析等高级功能,直接对标 Playwright

  2. 安全沙箱:供应链攻击越来越频繁,Bun 可能会引入 Deno 式的权限模型,至少对第三方脚本执行施加限制

  3. AI Agent 专用 API:既然 Anthropic 的核心业务是 AI,Bun 很可能推出针对 Agent 工作流优化的 API——比如更快的子进程创建、更高效的代码沙箱执行、与 LLM 的原生集成

  4. WebAssembly 组件模型支持:JSC 已经有了 WASM SIMD 优化,下一步可能是 WASI 和组件模型,让 Bun 成为多语言运行时

  5. 商业化托管服务:虽然 Jarred 说"Bun 当前零收入"并选择了 Anthropic 而非自己做云,但长期来看,Bun Deploy 或类似的边缘计算服务是自然延伸

结语

Bun 的故事不是"又一个 JavaScript 运行时"的故事。它是一个关于工程哲学选择的故事:用 Zig 而非 Rust,用 JSC 而非 V8,用 All-in-One 而非模块化——每一个选择都有代价,但合在一起形成了一个独特的价值主张。

被 Anthropic 收购不是终点,而是新的起点。AI 编程工具正在重新定义开发者的工作流,而运行时是这个工作流的基础设施层。Bun 在这个位置上的演进速度,可能比我们所有人预想的都快。

如果你还没试过 Bun,现在是最好的时机。不需要全面迁移,从 bun install 替代 npm 开始,或者用 bun test 替代 Jest——感受一下速度的差异,然后决定要不要走得更远。


参考资源

复制全文 生成海报 Bun JavaScript Zig JavaScriptCore Anthropic Node.js AI

推荐文章

Vue中如何使用API发送异步请求?
2024-11-19 10:04:27 +0800 CST
Vue3中的v-model指令有什么变化?
2024-11-18 20:00:17 +0800 CST
API 管理系统售卖系统
2024-11-19 08:54:18 +0800 CST
使用Python实现邮件自动化
2024-11-18 20:18:14 +0800 CST
Shell 里给变量赋值为多行文本
2024-11-18 20:25:45 +0800 CST
免费常用API接口分享
2024-11-19 09:25:07 +0800 CST
java MySQL如何获取唯一订单编号?
2024-11-18 18:51:44 +0800 CST
JavaScript设计模式:单例模式
2024-11-18 10:57:41 +0800 CST
Elasticsearch 文档操作
2024-11-18 12:36:01 +0800 CST
在 Rust 中使用 OpenCV 进行绘图
2024-11-19 06:58:07 +0800 CST
Boost.Asio: 一个美轮美奂的C++库
2024-11-18 23:09:42 +0800 CST
Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
联系我们
2024-11-19 02:17:12 +0800 CST
程序员茄子在线接单