返回博客

Vitest 4 升级后启动崩溃:rolldown native binding 在 Node 20 上悄悄缺失

给一个子包接入测试,npm install --save-dev vitest 一路绿灯,结果 vitest run 第一帧就挂,整个测试套件还没开始就已经结束了。错误栈看起来像是找不到 native 模块,令人费解——明明刚装完。

现象

启动报错,栈顶是 rolldownrequireNative

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

坑:EBADENGINE warning 是安装成功、运行必炸的前兆

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
正解:pin vitest@^3,等 Node 22 全量覆盖再升 v4

短期内最稳的办法是锁 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 成功不等于运行时能跑——EBADENGINE warning 是「native 包已被静默跳过」的唯一信号,看到它就要立刻警觉。