Vitest 4 升级后启动崩溃:rolldown native binding 在 Node 20 上悄悄缺失
给一个子包接入测试,npm install --save-dev vitest 一路绿灯,结果 vitest run 第一帧就挂,整个测试套件还没开始就已经结束了。错误栈看起来像是找不到 native 模块,令人费解——明明刚装完。
现象
启动报错,栈顶是 rolldown 的 requireNative:
Error: Cannot find module '@rolldown/binding-darwin-arm64'
Require stack:
- .../node_modules/rolldown/dist/shared/binding-C9S351wt.mjs
at requireNative (file:///.../rolldown/dist/shared/binding-*.mjs:219:22)
...
code: 'MODULE_NOT_FOUND',
cause: Error: Cannot find module './rolldown-binding.darwin-arm64.node'
回翻安装日志,能找到几行被轻易忽略的 EBADENGINE 警告:
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: 'vite@8.0.13',
npm warn EBADENGINE required: { node: '^20.19.0 || >=22.12.0' },
npm warn EBADENGINE current: { node: 'v20.14.0', npm: '10.7.0' }
}
vitest --version 显示 v4.x,Node 版本是 v20.14.0。
根因
vitest 4.x 把内部打包器从 esbuild/rollup 换成了 rolldown——这是用 Rust 写的高性能打包器,依赖平台专属的 native binding 包(@rolldown/binding-darwin-arm64、@rolldown/binding-linux-x64-gnu 等)。
这些 binding 包在 npm registry 上声明了 engines.node: "^20.19.0 || >=22.12.0"。当 Node 版本不满足时,npm 不会报错中断安装,只会静默跳过 optional native 依赖,顺带发出 EBADENGINE warning。包安装"成功"了,但 rolldown 需要的那个 .node 文件根本不在磁盘上。
等到 vitest run 真正调用 rolldown,才在运行时爆出 MODULE_NOT_FOUND。
npm install 收到 EBADENGINE 警告并不会退出码非零,CI 不会因此失败,人工扫日志也容易忽略。但这条警告的真实含义是「native optional 包已被跳过,运行时大概率崩」——尤其当依赖方(vitest)在启动路径上必须 require 该 native 包时,没有任何 graceful fallback。
正解
方案 A(推荐):回退到 vitest@^3
vitest 3.2.x 仍使用 vite 6 + esbuild,不依赖 rolldown,完整兼容 Node 20.x。
npm uninstall vitest
npm install --save-dev vitest@^3验证:
npx vitest --version # 应显示 v3.x.x
ls node_modules/rolldown # No such file or directory方案 B:升 Node 到 22.12+
如果项目和 CI 环境允许,用 nvm 切换后重装:
nvm install 22.12 && nvm use 22.12
npm install --save-dev vitest # 重装,这次 binding 会正常落盘注意确保 CI runner、Docker 镜像、所有开发机都统一升级,否则本地跑通 CI 依然挂。
不推荐:手动补装 binding
# 看似能修,实际 lockfile 还原后 binding 再次消失
npm install @rolldown/binding-darwin-arm64短期内最稳的办法是锁 vitest@^3,不引入 rolldown 依赖链。待 CI 和本地 Node 全部升到 22.12+ 后,再迁 vitest 4.x,届时 native binding 会正常安装,升级阻力也会小很多。
附:monorepo 子包的额外坑
在 monorepo 或者有 public/ 目录的项目里跑 vitest,root 自动检测有时会误解析成不存在的路径,输出类似 RUN v3.x.x /path/to/package/public-fallback,然后报 No test files found。
子包目录加一个显式的 vitest.config.ts 锁定 root 即可:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
root: '.',
include: ['test/**/*.test.ts'],
},
})一句话外卖
npm install成功不等于运行时能跑——EBADENGINEwarning 是「native 包已被静默跳过」的唯一信号,看到它就要立刻警觉。