从 Bun 迁移到 Nub
将 Bun 项目迁移到 Node + Nub 的实用代码优先指南——运行时、脚本、环境文件、每个 Bun 特定 API 到其 Node 或 npm 生态系统替代品的完整映射、Dependabot,以及实际会咬人的陷阱。
本指南逐步讲解将项目从 Bun 运行时迁移到 Nub 驱动的原生 Node。迁移的形态是:Nub 在原生 Node 上直接运行你的 TypeScript——你项目固定的版本,按需提供——你的包管理器和锁文件保持原位,你代码调用的 Bun.* API 被替换为它们的 Node 或 npm 生态系统等价物。大部分工作是最后一部分——Bun 的全包内置全局 API 是没有精确 Node 对应物的接口,所以每个都需要有意的替换。
以下所有内容基于 Nub 今天实际的行为。Nub 尚未做到的地方,本指南直说而非掩盖。
运行时和 TypeScript 执行
Bun 直接运行 .ts 文件。Nub 也是——指向入口文件,它在原生 Node 上转译 TypeScript(和 JSX、enum、namespace、参数属性、emitDecoratorMetadata),无需 tsconfig 构建步骤也无需注册单独加载器。
bun run src/index.ts # 之前
nub src/index.ts # 之后裸文件没有 nub run <file> 形式——nub <file> 是文件运行器,nub run <script> 是 package.json 脚本运行器。保持两者清晰:nub run dev 运行 "dev" 脚本,nub src/server.ts 运行文件。
如果你需要对照未增强的 Node 比较——排除转译或运行时增强差异——nub --node <file> 在关闭 Nub 增强的纯 Node 上运行文件,同时仍使用你项目固定的 Node 版本。Truthy 的 NODE_COMPAT 环境变量做相同的事情,全树持久。
选择 Node 版本
Bun 捆绑自己的运行时,所以 Bun 项目从不选择 Node 版本。在 Nub 上你需要,因为 Nub 运行你的 Node。按 Node 生态系统其余部分的方式固定——.nvmrc、packageManager/devEngines 条目,或 nub node pin——Nub 提供并使用该版本。选择你的依赖实际支持的版本;一些包伸手到全局变量(Float16Array、web crypto 接口、structuredClone),它们只在给定 Node 主版本起存在,过旧的下限会在运行时而非安装时失败。不确定时,固定当前 LTS 或最新稳定版并设置现实的 engines.node 下限。
Nub 的运行时增强按 Node 版本分层:快速层(Node 22.15+)使用同步模块钩子,兼容层(18.19–22.14)使用异步加载器 worker 路径。两者都转译 TypeScript;机制不同。较新的 Node 是更平滑的路径。
包管理器和锁文件
Nub 的包管理器接口镜像 pnpm 的命令语法:
bun install # → nub install (别名: nub i)
bun add <pkg> # → nub add <pkg>
bun remove <pkg> # → nub remove <pkg>
bunx <pkg> # → nubx <pkg> (别名: nub dlx <pkg>)Nub 与你项目已有的工具锁文件兼容——它往返 npm、pnpm 和 bun 锁文件并读取 yarn 的——所以你不必转换锁文件来采用 Nub。CLI 语法是 pnpm 的,但锁文件轴是多 PM 的。如果你要 Nub 作为 Nub 身份项目拥有解析(只消费中性的、跨工具配置),丢弃 Bun 品牌的 bunfig.toml 并让 Nub 写入中性 .npmrc;如果你保持现任 PM,Nub 忠实镜像该 PM。
与 Bun 的一个真实差异:Nub 遵循 pnpm 的依赖模型,不自动安装 peer 依赖。在 Bun 下因 Bun 静默安装其 peer 而工作的包会显现为缺失——将 peer 显式添加到你的 package.json。这是迁移后最常见的安装意外。
脚本:并行和顺序
Bun 暴露 bun run --parallel 和 bun run --sequential 来扇出多个脚本。Nub 的 nub run 镜像 pnpm 的脚本语法,不是相同形态:
- 单个脚本名称运行该脚本:
nub run build。 /regex/字面量运行每个名称匹配的脚本,按package.json顺序:nub run "/^build:/"。默认匹配集并发运行(--sequential串行化它)。这是 pnpm 的正则脚本选择器,是 Nub 今天支持的原生多脚本形式。- 空格分隔的
nub run a b不是多脚本形式——与 pnpm 中完全一样,那运行脚本a并带b作为参数。不要期望nub run lint test运行两个脚本。
没有带显式命名脚本列表的原生 run-p / run-s(nub run --parallel lint test typecheck)。如果你的 Bun 设置依赖 --parallel <a> <b> <c>,可移植的替代是 npm-run-all2,它提供 run-p 和 run-s 并在 Nub 下干净运行:
// package.json
{
"scripts": {
"check": "run-p lint typecheck test", // 并行
"release": "run-s build publish" // 顺序
}
}nub run check如果你的脚本名称共享前缀,正则选择器是更轻的选项且无需依赖:命名为 check:lint、check:typecheck、check:test 并运行 nub run "/^check:/"。
环境变量文件
Bun 自动加载 .env、.env.local、.env.<mode> 并接受显式 --env-file。Nub 为文件运行器、nub run 和 nub watch 自动发现和加载你的 .env* 文件,并接受显式 --env-file:
bun --env-file=.env.production src/index.ts
nub --env-file=.env.production src/index.ts需了解的行为:
- 传递任何
--env-file标志抑制 Nub 的自动.env*发现——只有你命名的文件加载,匹配"显式即显式"约定。这与显式文件退出自动级联的方式一致。 - Nub 在 env 文件值中展开
${VAR}交叉引用(自动加载路径和显式标志路径都展开)。纯 Node 的--env-file解析器不展开;Nub 弥合该差距。 - Shell 环境仍优先于文件值(环境中已设置的变量不被覆盖)。
两个需规划的差距:
- Nub 目前不将
--env-file-if-exists实现为自身标志。如果你传递它,Nub 不将其视为 env 文件指令——它转发给 Node,Node 20.12+/22+ 为其运行的文件遵守它,但 Nub 自身的.env-合并层不会像对待--env-file那样拾取这些变量。优先使用--env-file和你知道存在的文件,或依赖自动发现(已跳过缺失文件)。 - Nub 的
--env-file适用于其上的运行;它不是通用的nub exec风格 env 注入器用于任意子进程。对于将 env 文件加载到非 Nub 运行入口的工具(例如 Prisma CLI 调用),dotenv-cli仍是可移植的答案。
关于 Bun 的 --env-file "级联"的说明
有时假设 Bun 的 --env-file=.env 展开为级联——也加载 .env.local、.env.development 等。它不。在 Bun 中,显式文件路径和默认级联是互斥的:当一个或多个 --env-file 参数存在时,Bun 只加载那些命名的文件(按逗号分割,后者优先)并完全跳过默认 .env/.env.local/.env.<mode> 发现。级联是你完全不传递 --env-file 时的默认行为。这与 Node 的单文件 --env-file 语义一致并与 Nub 的行为一致,所以这里没有需要特殊复现的东西——--env-file 在所有三个运行时中意味着"只加载这些文件"。
Shell 命令:Bun.$
Bun 的 $ 模板标签运行 shell 命令,带可链式结果 API。没有 Node 内置对应物。常见替换:
// 之前 (Bun)
import { $ } from "bun";
const out = await $`ls -la ${dir}`.text();// 之后 (Node) — tinyexec
import { x } from "tinyexec";
const { stdout } = await x("ls", ["-la", dir]);tinyexec 是小型、轻依赖的进程运行器,是大多数 $ 调用点的最轻直接替代。如果你的代码大量依赖 $ 的链方法(.text()、.quiet()、.nothrow()),在 tinyexec(或 execa,更重但功能完整)上约 100–150 行的薄包装保留调用形态,使你不必重写每个调用点。
Bun.$ 做的两件事是普通进程运行器不做的:它解析和运行嵌入的 mini-shell(管道、重定向、模板字符串中的 glob),并自动转义插值。tinyexec 和 execa 运行带参数数组的单个程序——它们不解释管道。对于管道命令,在 JavaScript 中重构为显式步骤(例如用 node:fs 读取目录而非 ls | grep),这也比将用户控制的字符串交给 shell 更安全。
数据库访问:Bun.sql
Bun 提供 Bun.sql,基于 postgres.js 建模的 Postgres 客户端。它没有 Node 等价物。两个干净的替换,取决于你已有的:
-
postgres(postgres.js) — 几乎零摩擦的交换,因为Bun.sql基于它建模。标签模板 API 基本相同;主要变化是你自行构造客户端并在进程关闭时调用sql.end()。import postgres from "postgres"; const sql = postgres(process.env.DATABASE_URL!); const rows = await sql`select * from users where id = ${id}`; await sql.end(); -
pg— 如果你已使用 Prisma(其驱动适配器拉入pg),重用pg进行原始查询避免添加第二个 Postgres 客户端:import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const { rows } = await pool.query("select * from users where id = $1", [id]);
如果你使用 Prisma,迁移主要是关于原始查询——你的 Prisma 模型不受运行时交换影响。选择一个原始查询客户端并一致使用。
完整的 Bun.* 和 bun:* API 映射
这是综合参考:每个 Bun 特定 API,按类别分组,附推荐的 Node 或 npm 生态系统替代品。从中读取三件事。首先,Bun "API" 的大部分是标准 Web API(fetch、WebSocket、Request/Response、Blob、URL、crypto.subtle、structuredClone、流)——那些在 Node 和 Nub 的运行时上存在,所以没有需要迁移的。其次,仅存在于使 TypeScript/JSX 运行的东西(Bun.Transpiler)在 Nub 下不必要,后者直接运行 TypeScript。第三,真正 Bun 独有的电池——Bun.* 服务器、数据库、FS、哈希和 shell 助手——每个映射到一个维护的 Node 原语或成熟的 npm 包,下方列出。每个调用点选择一个替换并保持一致。
命名单个包的地方,是截至撰写时的当前维护选项;标准库覆盖的地方,node:* 模块是首选。
进程、shell 和系统
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.$ (shell) | tinyexec 或 execa | 参见上方 shell 部分。tinyexec 是更轻的直接替代;execa 功能完整。两者都不解释管道——将管道重构为显式步骤。 |
Bun.spawn、Bun.spawnSync | node:child_process(spawn、spawnSync) | 如果你的代码将 .stdout 读取为 ReadableStream,则包装以重现 web-stream 结果形态。 |
Bun.which(cmd) | which | 或用 node:fs 手动针对 process.env.PATH 解析。 |
Bun.openInEditor() | open 或 launch-editor | 在用户编辑器中打开文件;无 Node 内置。 |
Bun.env | process.env | 直接重命名。 |
Bun.argv | process.argv | 直接重命名。 |
Bun.main | process.argv[1](解析后的)或 import.meta.main(Node 24+) | 入口模块路径。 |
Bun.version、Bun.revision | process.versions、process.version | Nub 运行 Node,所以报告 Node 版本。 |
文件系统和 globbing
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.file(path) | node:fs / node:fs/promises(readFile、createReadStream) | Bun.file 返回惰性 Blob 类句柄;最近的 Node 形态是 fs.openAsBlob(path)(Node 20+)获得真实 Blob,否则 readFile。 |
Bun.write(path, data) | node:fs/promises writeFile | 如果你依赖 Bun.write 的自动 mkdir 父目录,则包装(先 mkdir(dirname, { recursive: true }))。 |
Bun.stdin、Bun.stdout、Bun.stderr | process.stdin / process.stdout / process.stderr | Node 的流;如果你将它们读取为 web stream 则包装。 |
Bun.Glob | tinyglobby 或 fast-glob | node:fs 还有内置 glob/globSync(Node 22+)用于更简单模式。tinyglobby 是轻量的当前选择。 |
Bun.FileSystemRouter | find-my-way 或框架的路由器 | 基于文件系统的路由解析;大多数框架(Next、Nitro、Hono 带文件路由插件)提供自己的。 |
Bun.mmap | mmap-io | 内存映射文件;小众,原生插件。除非你特别需要 mmap,否则优先流式读取。 |
HTTP、网络和实时通信
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.serve | Hono(@hono/node-server)、Fastify 或 Express | node:http 是原始原语;框架是实际替换。Hono 在精神上最接近(Web 标准 Request/Response)并在 Node 上不变运行。 |
Bun.serve WebSocket 处理器 | ws | 标准 Node WebSocket 服务器。与你的 HTTP 框架的升级处理配对。 |
new WebSocket() (客户端) | 运行时内置 | WebSocket 客户端全局变量在现代 Node 和 Nub 的运行时上存在——无依赖。 |
Bun.listen、Bun.connect (TCP) | node:net(createServer、connect) | 直接标准库映射。 |
Bun.udpSocket | node:dgram | 直接标准库映射。 |
Bun.dns.lookup、Bun.dns.prefetch | node:dns / node:dns/promises | Node 中无 prefetch 原语;查找直接映射。 |
Bun.Cookie、Bun.CookieMap | cookie(解析/序列化) | 或你的框架的 cookie 助手。 |
Bun.CSRF.generate、Bun.CSRF.verify | csrf-csrf 或 csrf | 可用时的框架级 CSRF 中间件。 |
fetch、Request、Response、Headers、FormData | 运行时内置 | 标准 Web API — 在 Node 和 Nub 上存在;无需迁移。 |
数据库和存储
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.sql、Bun.SQL (Postgres) | postgres (postgres.js) 或 pg | 参见上方数据库部分。Bun.sql 基于 postgres.js 建模,所以它是近乎直接替代;如果你已通过 Prisma 拉入则用 pg。 |
bun:sqlite | node:sqlite (Node 22.5+) 或 better-sqlite3 | node:sqlite 是标准库同步 SQLite(实验性但功能可用);better-sqlite3 是成熟的、经过实战检验的选择,API 最接近。 |
Bun.redis、Bun.RedisClient | ioredis 或 redis | 两者都是维护的、功能完整的 Redis/Valkey 客户端。 |
Bun.s3、Bun.S3Client | @aws-sdk/client-s3 | 官方 AWS SDK;与任何 S3 兼容存储一起工作。 |
Bun.secrets | keytar 或通过小型原生插件的平台钥匙串 | OS 凭据存储访问;keytar 是成熟的跨平台选项。 |
哈希、加密和压缩
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.password.hash、Bun.password.verify | node:crypto scrypt 或 @node-rs/argon2 | Bun.password 默认 argon2id;@node-rs/argon2 匹配该算法。node:crypto 的 scrypt 是零依赖标准库路径。如果你特别需要 bcrypt 则用 bcrypt。 |
Bun.hash、Bun.hash.* (非加密) | xxhash-wasm 或 node:crypto | Bun.hash 是快速非加密哈希(wyhash 族);xxhash-wasm 是接近的、快速的等价物。稳定加密摘要用 node:crypto。 |
Bun.CryptoHasher | node:crypto createHash / createHmac | SHA/MD5/HMAC 的直接映射;流式 update/digest 形态相同。 |
Bun.sha | node:crypto createHash('sha256') | 便捷一次性 SHA。 |
Bun.gzipSync、Bun.gunzipSync | node:zlib(gzipSync、gunzipSync) | 直接标准库映射。 |
Bun.deflateSync、Bun.inflateSync | node:zlib(deflateSync、inflateSync) | 直接标准库映射。 |
Bun.zstdCompressSync、Bun.zstdDecompressSync(+ 异步) | node:zlib zstd (Node 22.15+ / 23.8+) | Node 最近添加了 zstd 支持;旧下限用 @mongodb-js/zstd 或 fzstd。检查你固定的 Node 版本。 |
crypto.subtle、crypto.randomUUID、crypto.getRandomValues | 运行时内置 | 标准 Web Crypto — 在 Node 和 Nub 上存在。 |
构建、转译和模块
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.Transpiler | 在 Nub 下不需要 | Nub 直接运行 TypeScript 和 JSX,所以进程内转译器 API 不必要。如果你确实需要在运行时转译字符串,用 oxc-transform 或 esbuild 的 transform。 |
Bun.build | esbuild、tsup 或 rolldown | 用于应用/库打包。tsup 包装 esbuild 用于常见库场景;rolldown 是基于 Rust 的 Rollup 兼容打包器。 |
Bun.plugin (加载器插件) | 打包器的插件 API (esbuild/rollup/rolldown 插件) | Bun 的运行时加载器插件模型没有 Node 等价物;将自定义加载器移入你的打包器或 module.register() 钩子。 |
Bun.resolveSync | import.meta.resolve() 或 node:module createRequire(...).resolve | import.meta.resolve 是标准 ESM 解析器;createRequire().resolve 用于 CJS 风格解析。 |
import.meta.dir、import.meta.file | import.meta.dirname、import.meta.filename | Node ESM 等价物(Node 20.11+/21.2+)。参见上方 import.meta 部分。 |
测试
| Bun API | 推荐替换 | 备注 |
|---|---|---|
bun:test | vitest 或 node:test | Vitest 在人体工程学上最接近(describe/it/expect、watch 模式、mocking)并在 Nub 下运行。node:test + node:assert 是零依赖标准库运行器。 |
bun:test mocking(mock、spyOn) | Vitest 的 vi.fn/vi.spyOn,或 node:test 的 mock | Vitest 的 mocking 是与 Bun 最接近的匹配。 |
bun:test 快照 | Vitest 快照 | 内置于 Vitest;node:test 无原生快照支持。 |
FFI 和原生
| Bun API | 推荐替换 | 备注 |
|---|---|---|
bun:ffi(dlopen、CString、…) | koffi | Node 的维护的、积极开发的 FFI 库(未维护的 ffi-napi 的后继者)。 |
Node-API (N-API 插件) | Node-API | 工作方式相同——N-API 插件是 Node 标准。用 node-gyp 或 napi-rs 为 Rust 构建。 |
bun:jsc (引擎内部) | 无等价物 | JavaScriptCore 特定;无 V8/Node 对应物。移除或在功能重叠处替换为 node:v8 / node:vm(堆快照、序列化)。 |
HTML、文本和解析
| Bun API | 推荐替换 | 备注 |
|---|---|---|
HTMLRewriter | node-html-parser 或 cheerio | 对于流式转换,html-rewriter-wasm 移植了 Cloudflare 的重写器。Nub 不提供内置重写器。 |
Bun.escapeHTML | escape-html | 小型,单一用途。 |
Bun.stringWidth | string-width | 终端显示宽度(处理宽字符、ANSI)。 |
Bun.stripANSI | strip-ansi | 移除 ANSI 转义码。 |
Bun.wrapAnsi | wrap-ansi | ANSI 感知的自动换行。 |
Bun.markdown | marked 或 markdown-it | Markdown 转 HTML。 |
Bun.color | culori 或 color | 跨格式颜色解析/转换。 |
Bun.semver | semver | 权威 semver 库;Bun 的 API 是子集。 |
Bun.TOML.parse | smol-toml 或 @iarna/toml | TOML 解析。 |
Bun.YAML.parse | yaml | YAML 解析/序列化。 |
Bun.Image | sharp | 图像解码/编码/转换。 |
工具、计时和检查
| Bun API | 推荐替换 | 备注 |
|---|---|---|
Bun.inspect | node:util inspect | 直接映射;相同的深度/颜色选项。 |
Bun.deepEquals、Bun.deepMatch | node:util isDeepStrictEqual,或 dequal | isDeepStrictEqual 用于严格相等;dequal 用于快速松散相等检查。 |
Bun.sleep、Bun.sleepSync | node:timers/promises setTimeout(ms) | await setTimeout(ms) 用于异步;真正同步睡眠用 SharedArrayBuffer 上的 Atomics.wait(很少需要)。 |
Bun.nanoseconds | process.hrtime.bigint() | 高分辨率单调时钟。 |
Bun.randomUUIDv7 | uuid(v7) | crypto.randomUUID() 内置但发出 v4;uuid 的 v7 用于时间排序 ID。 |
Bun.peek | 无等价物 | 同步读取已结算 promise 的值;重构为 await 或自行跟踪解析。 |
Bun.fileURLToPath、Bun.pathToFileURL | node:url(fileURLToPath、pathToFileURL) | 直接标准库映射。 |
Bun.readableStreamTo*(Text、JSON、Blob、ArrayBuffer、Bytes、Array、FormData) | 直接消费 web stream | new Response(stream).text()/.json()/.blob()/.arrayBuffer()/.formData() 覆盖大部分;node:stream Readable.fromWeb 桥接到 Node 流。 |
Bun.deflate/buffer sink(Bun.ArrayBufferSink、Bun.allocUnsafe、Bun.concatArrayBuffers) | Buffer(Buffer.allocUnsafe、Buffer.concat)和 node:stream | Node 的 Buffer 覆盖分配/拼接助手;用 PassThrough 流进行 sink 式累积。 |
Bun.gc、Bun.generateHeapSnapshot | node:v8(writeHeapSnapshot、--expose-gc 用于 global.gc) | 堆快照和手动 GC;GC 需要 --expose-gc 标志。 |
Bun.stdin/stdout 字节 sink、Bun.indexOfLine | node:buffer / node:readline | 通过流上的 node:readline 进行行扫描。 |
Bun.cron (调度) | croner 或 node-cron | 进程内 cron 调度。croner 是积极维护的、无依赖选择。 |
高频 API(Bun.file、Bun.write、Bun.spawn、Bun.$)的实用迁移策略:编写一个小 shim 模块,在你代码已使用的调用形态下重新导出 Node 原语,在所有使用这些 API 的地方导入它,差异保持局限到那个文件而非分散到每个调用点。
import.meta 和 CJS 全局变量
Bun 和 Node 在这里已收敛,所以这些是 ESM 中的机械重命名:
| Bun | Node (ESM) |
|---|---|
import.meta.dir | import.meta.dirname |
import.meta.file | import.meta.filename |
import.meta.main | import.meta.main (Node 24+) — 否则将 process.argv[1] 与解析的模块路径比较。 |
__dirname (在 ESM 中) | import.meta.dirname |
__filename (在 ESM 中) | import.meta.filename |
生产服务器和 Docker
如果你的服务器运行时目标是 Bun(例如 Nitro 的 bun 预设等框架预设),切换到其 Node 目标(node-server / node)并用 node 运行构建入口。Docker 基础镜像从 Bun 镜像改为 Node 镜像加全局 Nub 安装:
FROM node:26-slim
RUN npm install -g @nubjs/nub
# ... 复制应用, nub install, build ...
CMD ["node", ".output/server/index.mjs"]直接用 node 运行已构建、已转译的服务器包;在构建阶段(安装、运行脚本、运行 TypeScript 入口)使用 Nub,那里它的增强物有所值。
CI 和 Dependabot
- CI 设置: 用 Nub 的替换 Bun 设置 action。参见 setup-nub 页面了解 GitHub Action。
- Dependabot / Renovate: 这些 bot 没有原生 Nub 支持。使用
package-ecosystem: "npm"让 bot 理解你的清单,并添加一个在 bot 更新后运行nub install的工作流步骤以 Nub 的格式重新生成锁文件。提升package.json但无法重新生成锁文件的 bot 否则会使两者不同步。
供应链冷却(minimumReleaseAge)
Bun 的 bunfig.toml 支持 [install].minimumReleaseAge——拒绝安装比阈值更新的包版本的供应链加固门。Nub 遵守相同门。作为 Nub 身份项目它位于 .npmrc 中为 minimum-release-age,以分钟为单位:
# .npmrc
minimum-release-age=1440 # 一天如果你迁移 bunfig.toml,注意单位变化:bunfig 以秒表达 minimumReleaseAge,nub install 将其转换为引擎的分钟(向上取整以使小的非零门不收缩为零)。Bun 的 minimumReleaseAgeExcludes 映射到 Nub 的 minimumReleaseAgeExclude。
值得预先防范的陷阱
- Peer 依赖不自动安装。 与 Bun 最大的行为差异。显式添加缺失的 peer;预期不止一个会浮现。
nub run a b不是多脚本。 它运行a并带b作为参数(与 pnpm 和 Bun 的普通run相同)。使用/regex/选择器或npm-run-all2进行真正扇出。--env-file抑制自动发现。 一旦你传递显式文件,该运行的自动.env*级联关闭——传递你需要的每个文件,或依赖自动发现。--env-file-if-exists尚不是一等 Nub 标志。 使用--env-file和你知道存在的文件,或自动发现(已容忍缺失文件)。- 固定现实的 Node 版本。 Nub 运行你固定的 Node;过旧的下限在运行时因缺失全局变量失败,而非安装时。固定当前 LTS 或稳定版并设置
engines.node。 - 内存特征与 Bun 不同。 Node 对于相同的并行扇出通常比 Bun 使用更多内存。如果你在 Bun 下并行化了多个重任务(类型检查、lint、测试)并在迁移后遇到 OOM kill,限制并发(例如同时最多运行两个重任务,并将最耗内存的排在最后)。
- 良性
module.register()弃用警告 可能在新 Node 上从加载器钩子出现;它无害并随工具采用module.registerHooks()解决。用--no-warnings或特定--disable-warning静默(如果嘈杂)。