Agent Developer Roadmap
JavaScript 运行时

Bun:JavaScriptCore、一体化工具链与 Node 兼容工程

核验日期:2026-07-16。本文以 Bun 1.3 稳定线为观察对象;官方 GitHub Release 在核验期内列出 1.3.x 更新。Bun 兼容状态变化快,具体模块必须在目标 patch 上重新核对 Node.js CompatibilityReleases

Bun 把 runtime、package manager、script runner、test runner、bundler 和大量宿主 API 放进一个原生可执行文件。它用 JavaScriptCore(JSC)执行 JavaScript,以 Zig 实现大量宿主和工具链逻辑,并以“运行现有 Node 项目”为重要目标。这带来更少的工具进程和较低启动开销,也带来一个必须认真管理的事实:相同命令名或 Node API 外形不代表内部引擎、序列化格式、调试协议和边界行为相同。

学习结果

完成本文后,读者应能:

  • 区分 JSC、Bun runtime、Bun.* API、Web APIs、Node compatibility 和内置工具的责任。
  • 解释 .ts/.tsx 即时转译、ESM/CJS 互操作、Node-style resolution 与 Bun auto-install 的边界。
  • 把“用 bun install”“用 bun test”“用 Bun runtime”拆成三个可独立回滚的迁移决定。
  • 验证项目实际使用的 node:*、globals、native addon、worker、stream、inspector 和 framework,而不是引用总兼容率。
  • 使用 Bun.serve 实现标准 Request/Response handler,并处理 stream、idle timeout、取消、错误和关闭。
  • 设计同条件性能实验,避免把 Bun 官网的特定 Linux Hello World/HTTP 数据当作业务结论。

架构分层

JavaScriptCore 负责语言执行、JIT 和垃圾回收。Bun 的 Zig 宿主实现事件循环、I/O、模块、转译和许多兼容 API。node:v8 即使存在也不能把 JSC 变成 V8;官方兼容页明确指出某些 node:v8 序列化使用 JSC wire format,V8 专属 profiling 应改用 Bun/JSC 对应工具。依赖若读取 V8 内部、依赖 undocumented Node binding 或 monkey-patch loader,风险远高于只用 path/fs/events 的包。

一条命令的多重含义

bun run app.ts 可能表示:解析 script 或文件、即时转译 TypeScript、解析 npm 依赖、加载 ESM/CJS、由 JSC 执行。bun install 只替换 package manager,不要求生产 runtime 是 Bun;bun test 只替换 test runner;bun build 生成产物也不自动规定目标 runtime。

迁移层输入变化可独立收益主要风险回滚
包管理器lockfile、install/linker、lifecycle scripts安装速度、全局 cache、workspacelock 语义、peer、脚本、registry保留原 lock/CI lane
script runnernpm runbun run启动和脚本调度PATH、shell、参数转发、env改回原命令
test runnerJest/Vitest → bun test内置 TS、watch、Jest-like APImock/timer/snapshot/DOM/plugin 差异双跑关键 suite
bundler原 bundler → bun build单 binary、原生 transformplugin、CSS/assets、target/Source Mapartifact 契约对照
runtimenode appbun app启动、部分 I/O/内存收益Node API、JSC、addon、diagnostics同产物双 runtime canary

最小风险做法是一次只替换一层。若同时换 install、test、build 和 runtime,失败时无法确定拥有者,回滚面也扩大。

启动与 TypeScript 即时转译

bun --version
bun run index.ts
bun index.ts

后两条都能执行文件。Bun 会即时转译 .ts/.tsx/.jsx,这不等价于 TypeScript type checking。生产 CI 仍应运行项目选定的 tsc --noEmit 或等价 checker。比较 Node + compiled JS 与 Bun direct TS 时,必须说明是否把转译时间算入,是否预热 cache,是否两侧都做类型检查。

type RuntimeInfo = { name: string; engine: string };

const info: RuntimeInfo = { name: "Bun", engine: "JavaScriptCore" };
console.log(JSON.stringify(info));

环境 Bun 1.3;bun run info.ts 预期输出对象。把 engine 改为数字,运行时仍可能转译并执行,静态类型检查才应拒绝。这一负例应该进入 CI 设计,而不是误称“Bun 原生 TS 会检查类型”。

模块解析与 ESM/CJS

Bun 支持 ESM 和 CJS,并实现 Node-style package resolution;官方文档说明 ESM 中也可使用 require 等扩展。宽松互操作便利迁移,却可能让只在 Bun 生效的代码进入共享包。跨 Node/Deno/Edge 的库应坚持标准 ESM 或明确 dual-package 边界,不把 Bun 扩展当通用 ECMAScript。

解析输入包括 specifier、importer、package type/exports/imports、conditions、扩展名、tsconfig paths、node_modules、Bun config 和 auto-install 状态。调试时保存这些输入。

{
  "type": "module",
  "exports": {
    ".": {
      "bun": "./dist/bun.js",
      "node": "./dist/node.js",
      "default": "./dist/web.js"
    }
  }
}

自定义 bun condition 可提供宿主优化,但会扩大测试范围。只有宿主差异真实存在时才添加;共享实现优先。每个条件都要测试实际 resolver 选择、public API 等价和发布文件存在。

Auto-install 的便利与可复现性

Bun 在特定条件下可在运行时解析并安装缺失 package。开发试验很方便,生产和 CI 则应显式 bun install --frozen-lockfile(具体 flag 以当前文档为准),禁止请求路径触发网络安装。否则 registry 可用性、最新解析和供应链变化进入启动/请求正确性。

全局 cache 减少重复下载,但 cache 不是 lock。冷/热安装 benchmark 要分离 network、cache、解压、link 与 lifecycle scripts。不要通过删除 node_modules 切换解析策略后仍称“同一条件”。

Node 兼容:用项目验证范围替代形容词

Bun Node.js Compatibility逐模块列出 fully、partially、not implemented,并说明它会在每次 release 前运行大量 Node test suite。核验时页面仍列出一些部分实现或缺失:例如部分 async_hooks/promise hooks、worker options、inspector、node:test 能力和 V8 专属接口。列表随 patch 变化,文章不把瞬时百分比写成永久结论。

项目验证范围至少覆盖:

fixture通过条件
模块ESM、CJS、top-level await、conditional exports、JSON/Wasm解析文件和 namespace 与预期相同
核心 API实际使用的 fs/http/stream/crypto/tls/worker成功、错误、取消、边界值一致
Globalsprocess/Buffer/timers/URL/fetch类型与关键行为一致,不依赖 stub
npmframework、ORM、logger、APM官方 integration test 与代表请求通过
NativeNode-API addon、postinstall binary目标 OS/arch/ABI 启动和错误路径通过
Diagnosticssource map、inspector、profile、async context事故所需 trace/profile 能取得

“导入成功”只是第一层。有些 compatibility polyfill/stub 允许模块加载,但调用方法时 noop 或失败;必须执行使用路径。也不要用 Bun 自己的 API 改写 fixture 后声称 Node compatibility 已通过,那验证的是迁移方案而非兼容。

Node-API 与原生依赖

Bun 支持 Node-API 的目标是运行大量现有 addon,但 native code 仍可能依赖 V8、libuv、ABI、线程或构建脚本细节。列出 .node 文件、安装脚本、prebuilt 平台组合,在生产镜像上执行。原生崩溃可终止整个 Bun 进程;通过 JS try/catch 无法恢复内存破坏。

事件循环、microtask 与 JSC

Bun 实现 Node 与 Web 风格异步 API,但不应假设所有 callback/timer 的偶然顺序与 Node 完全一致。业务协议只依赖规范保证;对 process.nextTicksetImmediate、Promise、I/O callback 的混合顺序建立 fixture,升级 Bun patch 时回归。

setTimeout(() => console.log("timer"), 0);
setImmediate(() => console.log("immediate"));
queueMicrotask(() => console.log("microtask"));
Promise.resolve().then(() => console.log("promise"));
console.log("sync");

稳定预期是 sync 先于异步输出,microtask 在相应 checkpoint 运行;timer 与 immediate 的精细顺序不要写入业务逻辑。无限 microtask 或同步 JSON/正则同样会阻塞 Bun 的 JavaScript 执行。引擎更快不能修复 O(n²) 算法和无界并发。

Bun.serve:标准 handler 与宿主扩展

type Technology = { id: string; name: string };

const file = Bun.file(new URL("./data/technologies.json", import.meta.url));

const server = Bun.serve({
  hostname: "127.0.0.1",
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);
    if (request.method !== "GET") {
      return Response.json({ code: "METHOD_NOT_ALLOWED" }, {
        status: 405,
        headers: { allow: "GET" },
      });
    }
    if (url.pathname !== "/technologies") {
      return Response.json({ code: "NOT_FOUND" }, { status: 404 });
    }
    try {
      const items = await file.json() as Technology[];
      const query = url.searchParams.get("query")?.toLowerCase() ?? "";
      return Response.json(items.filter((item) => item.name.toLowerCase().includes(query)));
    } catch (error) {
      console.error("catalog_read_failed", { name: error?.name });
      return Response.json({ code: "CATALOG_UNAVAILABLE" }, { status: 503 });
    }
  },
});

console.log(`ready ${server.url}`);

环境 Bun 1.3;运行 bun run server.ts;请求 /technologies?query=bun 预期返回匹配数组。生产版应在启动时读取和验证稳定数据,不每请求重新 parse。handler 使用标准 Request/Response,文件和监听使用 Bun 宿主 API,迁移边界清楚。

路由、idle timeout 与 streaming

Bun 当前 Bun.serve 还支持 routes、WebSocket、server metrics、per-request timeout、graceful stop 等。官方文档说明默认 idle timeout 与最大配置会影响安静的长响应/Server-Sent Events;长流需要明确 per-request 策略。不要把 idle timeout 当总业务 deadline:它衡量一段时间没有发送/接收数据,与整个请求时长不同。

大响应返回 ReadableStream,让 backpressure 传播;不要 await response.text() 后再转发。客户端断开、上游 abort、stream error 和已发送 headers 后错误需要单独测试。HTTP API 看似 Web 标准,socket pool、TLS、压缩和 header 行为仍由 Bun 宿主拥有。

关闭

await server.stop() 可等待在途请求,强制选项会终止连接。生产 shutdown 流程仍需要:先摘 readiness/停止新请求,给 in-flight deadline,停止 worker/定时器/数据库连接,flush 有界 telemetry,超时强制退出。容器 grace period 应大于应用 deadline。

文件、流与二进制

Bun.file 延迟表示文件,可作为 Blob/Response body,减少常见样板。便利不移除路径和权限问题:固定 root、规范化路径、拒绝 traversal、处理不存在/权限/替换竞争。Bun 默认不像 Deno 那样要求 capability flags,容器和 OS 权限是主要边界。

Web Streams、Blob、ArrayBuffer 与 Node streams 可互操作,但注意复制、ownership、object mode、错误和取消。迁移高吞吐路径时用慢客户端测试 RSS,确认没有隐式全量 buffering。官方兼容页若指出某个 outgoing request body 当前缓冲,就不能假设 Node streaming test 自动等价;以目标 patch 实测。

Workers 与 CPU 任务

Bun 提供 Web Worker/Node worker compatibility。适合把持续 CPU 工作移出请求主线程,但同样应创建有界 pool,不按请求无限新建。核对 worker_threads 具体 options、transfer list、SharedArrayBuffer、async context 和 termination 行为,因为兼容页可能标为部分实现。

JSC 与 V8 的优化策略不同,同一 CPU microbenchmark 结果可能不同。真实任务必须包含数据转换和消息成本。若依赖 V8-specific WebAssembly/serialization/profile 行为,先确认 Bun 支持而不是只测纯循环。

Package manager:lock、linker、scripts 和 registry

bun install 可用于仍由 Node 运行的项目。这通常是最容易隔离的第一步,但需要审查:Bun lockfile 是否提交;frozen install 是否在 CI 强制;workspace/peer/optional dependency 是否一致;private registry、proxy、CA 和 auth 是否工作;lifecycle script trust 是否符合策略;Docker layer cache 是否稳定。

安装性能比较必须使用相同 registry mirror、lock、cache state、文件系统和脚本策略。一个工具跳过脚本而另一个执行脚本,不能只比较秒数。正确性通过后记录 cold cache、warm global cache、已有 node_modules 三组。

供应链控制至少包括锁版本、审计 transitive dependencies、限制脚本、校验发布来源、最小 registry token 和不在日志输出 .npmrc。Bun 内置 audit/security 能力随版本演进,不能替代组织策略。

Test runner:Jest-like 不等于 Jest identical

bun:test 支持 TypeScript-first、watch、mock、snapshot 等常用能力。迁移先对纯单元测试双跑,再检查 fake timers、module mocking、snapshot serializer、coverage、DOM 环境、test isolation、open handles 和 reporter。框架专属 plugin 可能依赖 Vitest/Jest transform pipeline,不能用 API 名相似推断。

import { expect, test } from "bun:test";

function filter(names: string[], query: string) {
  return names.filter((name) => name.toLowerCase().includes(query.toLowerCase()));
}

test("过滤忽略大小写", () => {
  expect(filter(["Node.js", "Bun"], "BUN")).toEqual(["Bun"]);
});

命令 bun test,预期一项通过。测试核心逻辑不需网络和文件权限。跨 runtime 共享测试可使用标准 assert 或分别提供薄 adapter;不要为了 runner portability 自研测试框架。

Bundler 与 runtime 的边界

bun build --target=bun 可生成 Bun 目标,HTML import/Bun.serve 还能结合前端资产。构建成功只证明产物生成,不证明 Node 兼容或生产 runtime 行为。保存 entry、target、external、Source Map、assets manifest 和动态 import 契约;与构建工具栏方法一致,从产物到 runtime 加载验收。

如果项目已有 Vite/webpack/Rollup 且没有可测痛点,只替换 runtime 不必同时换 bundler。Bun plugin API 与现有生态 plugin 不同,迁移成本应按实际 Loader/Plugin 盘点。

内置 SQLite、SQL、S3 等 API 的取舍

Bun 暴露 bun:sqlite、SQL、S3 等宿主能力,减少依赖和跨 JS/native 桥接。选择时比较:协议/功能覆盖、事务和并发语义、连接池、迁移工具、可观测性、认证、兼容平台与退出处理。内置不是自动更可靠,也不等于对应服务免费。

贯穿项目只读 JSON 不需要引入数据库。等后端栏确立事务/一致性需求后再选择驱动,这符合最小实现原则。

安全边界

Bun 进程默认拥有 OS 用户可访问的文件、网络、环境与子进程能力。使用容器/用户权限、只读 rootfs、network policy、seccomp(按平台)、secret mount 与资源 limit 建立外层边界。应用层仍要处理路径、SSRF、header/body limit、命令注入、prototype pollution 和错误泄露。

Bun Shell/child process 使用时,永远不要把不可信输入插值成 shell 程序。选择参数数组和 allowlisted executable。auto-install 与 lifecycle scripts 不进入生产请求路径。native addon/FFI 视为高信任代码。

诊断与可观测性

Bun 支持 debugger、JSC profiling 和部分 Node inspector/async hooks;兼容页会标出缺口。采用前先验证现有 OpenTelemetry/APM/logger 是否能保留 async context、source map 和 CPU/heap profile。监控至少包括:请求率/错误/p95/p99、event-loop lag 或等价指标、CPU、RSS/heap、GC、连接、worker queue、部署版本与重启原因。

日志结构与其他 runtime 保持同契约:request id、stable event name、status/duration、safe error code。不要用高频 console.log 做 benchmark;日志开销在各 runtime 不同且会改变尾延迟。

性能评估

Bun 官方文档给出特定 Linux Hello World 的启动和 HTTP 数据。这些是可复现线索,不是对任意业务“快几倍”的保证。自己的实验依次:

  1. 通过功能、错误、stream、timeout 与 shutdown 契约。
  2. 固定 Bun/Node/Deno patch、硬件/容器、依赖、转译、source map、日志。
  3. 冷 CLI 分离 binary start、module load、TS transform、dependency cache。
  4. HTTP 固定协议、keep-alive、body、并发、连接数,记录 p50/p95/p99 和 errors。
  5. CPU 任务加 worker/message,I/O 任务加上游与连接池,不混成一个数字。
  6. 记录 RSS/heap/GC 与成本;运行足够久观察 JIT 和泄漏。

更高吞吐若伴随不兼容 API、缺失 profile 或运维团队不熟悉,未必降低总成本。反之,短命 CLI/测试/边缘辅助任务可能特别受益于启动与一体化工具,适用场景要具体。

故障排查

现象最可能边界最小证据修正
Node 成功、Bun import 失败exports/loader/unsupported builtinspecifier、importer、compat 状态显式入口、升级/回退、最小 adapter
安装很快但运行缺 binarylifecycle/optional/nativeinstall log、目标文件、OS/arch审查脚本和 prebuild,不跳过正确性
测试大多通过但 mock 失败runner 语义/transform pipeline单一 test fixture保留原 runner 或改用支持 API
大响应内存尖峰隐式 buffering/backpressureslow-client + RSS/profile使用真实 stream,确认 API 支持
APM 无 traceasync hooks/inspector 缺口最小跨 await/worker trace使用 Bun 支持集成或保留 Node
热重载后重复副作用模块生命周期/dispose 不完整timer/listener 数量显式清理,生产不用热重载证明

迁移门禁

建议按可回滚层次推进:先让 bun install 在独立 CI lane 生成并验证依赖;再试 script/test runner;然后用相同 compiled server 在 Node/Bun 双跑;最后考虑 Bun-specific API。每一步保留原命令与 lock,设定退出条件,例如关键 package 不兼容、APM 缺少必需诊断、p99/内存无收益或团队无法承受双平台维护。

新项目若选择 Bun,可用标准 ESM/Web APIs 保持核心可迁移,仅在宿主适配层使用 Bun.serve/file/sqlite。不要为了可能迁移提前写大型 runtime abstraction;一个纯查询函数和三个小 adapter 足够。

官方资料

交付检查

完成 Bun 示例时应留下:精确 patch/OS/arch;package manager、runner、test、build、runtime 各自是否替换;lock/frozen install;实际 Node API、Globals 与 addon 验证结果;标准 Request/Response 服务;stream/idle timeout/cancel/shutdown 测试;JSC/diagnostics/APM 验证;cold/warm 与 p95/p99/RSS 原始样本;明确回退 Node 的命令和门槛。

分层迁移演练:一次只换一个所有者

准备现有 Node 项目的可重复的现状记录:frozen install、类型检查、单元/集成测试、生产 build、启动到 ready、代表请求、stream、SIGTERM、p95/p99、RSS 和诊断采集。把命令与原始结果提交到迁移记录,而不是只截图“更快”。

第一阶段只用 bun install,仍以 Node 执行测试和服务。检查依赖树、peer/optional、workspace、bin、lifecycle、private registry 与 lock diff。若失败,问题属于 package manager,不修改 runtime 代码。第二阶段只替换 script/test runner,双跑关键 suite;对 mock、timer、snapshot、coverage 和 open handle 的差异建立最小 fixture。第三阶段用相同业务代码切 Bun runtime,保留原 build/test;这时才检查 JSC、Node APIs、streams、worker、native 和 APM。

每阶段都要能用一处配置/命令回滚。不要在同一提交重写 Bun.serve、切 bun:test、生成新 lock 并替换 bundler。那会把四个独立失败域折叠成不可诊断的大迁移。只有标准 handler 已稳定且 Bun runtime 通过后,才评估 Bun.file/sqlite/sql 等专属 API;引入每个专属 API都记录迁移出口。

兼容性缺口分类

遇到 Node 成功、Bun 失败,先把缺口归类。解析类保存 specifier、importer、conditions 与实际文件;API 类保存最小输入、结果/错误和官方兼容状态;时序类保存回调顺序和版本;native 类保存 addon/ABI/OS/arch;诊断类保存 agent 初始化与缺失 span/profile。分类后才能选择升级 Bun patch、换依赖、加薄 adapter 或保留 Node。

不要用 catch 所有异常并返回默认值“修复”兼容,因为它把真实错误变成数据损坏。若官方页面把模块标为部分实现,查看项目实际调用的方法;未调用的缺口不是阻塞,调用到 stub/noop 则是硬问题。向上游报告时提供单文件最小复现、Bun/Node 精确版本和期望行为,避免提交整个业务仓库。

JSC 诊断演练

为一次 CPU 长尾准备 Bun 支持的 CPU profile/DevTools 流程,确认 Source Map 能指回 TS;为内存增长准备 heap/JSC 工具或至少 RSS/heap 分解;为 async trace 验证跨 await、timer、fetch 和 worker 的上下文。若现有 APM 只能在 Node/V8 完成关键诊断,这是选型成本,不是“以后再补”的小项。

运行 profile 时关闭无关日志并记录 profiling 开销。比较 Node/V8 与 Bun/JSC 的热点名称不能只看相同函数采样百分比,因为 JIT/inlining 与采样器不同;关注完整请求 CPU、wall、吞吐和尾延迟。修正算法/分配后在无 profiler 条件复测。

生产容量与升级

Bun patch 更新频繁,生产镜像固定精确版本而不是浮动 latest。升级前重跑 Node compatibility fixture、native addon、HTTP stream/timeout、worker、test/build 与 diagnostics。canary 观察错误码、p99、RSS、重启和 CPU;保留旧 binary/镜像/lock。若升级只为性能,正确性或可观测性任一退化都应阻止推广。

容量计划记录启动、稳态与峰值,而不是只看官方 Hello World。短 CLI 重点是 binary/module/transform;服务重点是连接、stream、JIT/GC、worker queue 与 shutdown;package manager 重点是 cold/warm cache 和正确安装。三类数字不可混用。

上一篇:Deno · 返回专栏导读 · 下一篇:Edge Runtime

在 GitHub 上编辑此页

本页目录