Chapter 08

Node 兼容与迁移

把一个现有 Node.js 项目直接换成 Bun 跑——弄清哪些 100% 兼容、哪些有坑、怎么调试。

8.1 兼容性总览

Bun 2.0 对 Node.js API 的兼容程度(Bun 官方自测 + 社区验证):

模块/功能兼容度备注
node:fs, fs/promises~99%极少数 edge case 行为略异
node:path, node:url100%
node:http, https~98%推荐新代码用 Bun.serve
node:net, tls~95%
node:stream~95%复杂 duplex 场景需测
node:events100%
node:crypto~97%
node:child_process~95%推荐 Bun.spawn
node:worker_threads
node:cluster✅ (1.2+)
node:vm部分
CommonJS(require)100%可与 ESM 混用
node-gyp 原生模块(.node)大部分可用需重编译某些
Express / Fastify / Koa✅ 直接跑
Next.js部分建议先看官方状态
Prisma需 trustedDependencies

查官方最新兼容表:bun.sh/docs/runtime/nodejs-apis

8.2 process / globalThis

process.argv                    // ✅
process.env                     // ✅
process.cwd()                   // ✅
process.platform / arch         // ✅
process.versions.bun            // Bun 特有,Node 下为 undefined
process.versions.node           // Bun 伪装成兼容 Node 版本(为第三方库)

globalThis.Bun                 // 全局 Bun 对象

8.3 CommonJS 与 ESM 互操作

Bun 里 CJS 和 ESM 可以互相 import:

// CJS 文件
const fs = require("node:fs");
module.exports = { greet: () => "hi" };

// ESM 文件
import mod from "./cjs-file.cjs";   // ✅ 能 import
import { readFileSync } from "./cjs-file.cjs";  // ✅ 能解构

这比 Node 自己的兼容更宽松——Node 从 CJS 到 ESM 的互操作限制更多。

8.4 typescript 配置

bun init 生成的 tsconfig 已含 Bun 类型。手动配时关键项:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "bundler",
    "types": ["bun-types"],      // 或 npm i -D @types/bun
    "lib": ["esnext", "dom"],
    "allowJs": true,
    "strict": true,
    "noEmit": true           // Bun 不需要 tsc 编译
  }
}

8.5 替换清单:迁移一个 Node 项目

第一阶段:不改代码,直接跑

$ bun install
$ bun run start

多数情况直接就能跑。不能跑通常是:

  1. 某个 postinstall 被 Bun 默认拒绝——在 package.json 加 trustedDependencies
  2. 某个 CJS 原生模块未编译——运行 bun install --force 或查清楚包名加到 trustedDependencies
  3. 用了某个尚不兼容的 Node API——查 bun.sh/docs 对照

第二阶段:渐进式用 Bun 原生 API

NodeBun 原生(更快)
fs.readFileBun.file(...).text()
fs.writeFileBun.write(path, data)
http.createServerBun.serve({ fetch })
child_process.spawnBun.spawn
bcryptBun.password
better-sqlite3bun:sqlite
wsBun.serve + websocket
dotenv自动加载
jestbun test

每次换一个,独立 commit——方便 rollback。

8.6 常见坑

坑 1:postinstall 被默认跳过

现象:装了某包但用不了,检查 node_modules 里对应目录是空的。解决bun pm untrusted 查看被阻止的脚本,然后 bun pm trust <package>

坑 2:node-gyp 编译失败

现象node-gyp rebuild 错误。一般是 Bun 没能自动触发编译,或 .node 原生模块与 Bun 的 Node-API 版本不匹配。解决

坑 3:__dirname / __filename 在 ESM 下不存在

Node ESM 里要用:

import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Bun 里也同理。或直接用 import.meta.dir/import.meta.file(Bun 扩展)。

坑 4:某些包只支持 V8

v8-profiler、某些 monkey-patch V8 内部的工具——Bun 的 JSCore 跑不了。这类包通常非核心业务(性能分析、IDE 工具)。

8.7 调试

断点调试

$ bun --inspect src/server.ts
# 打开返回的 chrome://inspect URL 连接 Chrome DevTools

$ bun --inspect-wait src/server.ts
# 等待调试器连接才执行(便于打第一条断点)

VS Code 用 Bun 扩展即可原生支持 Launch / Attach。

日志

console.logconsole.tableconsole.dir 与 Node 基本一致,但 Bun 的 Bun.inspect(x) 更强大,支持循环引用、带颜色。

8.8 性能剖析

$ bun --hot --inspect=0.0.0.0:9229 server.ts
# 然后在 chrome://inspect 里 CPU profile

# 采样型 profiler(v1.2+)
$ bun run --profiler src/server.ts

8.9 与 pm2 / forever 等进程管理器

Bun 可以被 pm2 管理(当 Node 使):

pm2 start "bun run start" --name api --interpreter none

或使用 Bun 1.2+ 的 node:cluster 模式做多进程。

8.10 小结