Nx 工作区 Vite 7 升级到 Vite 8 完整迁移指南:Rolldown 替换 Rollup 的配置改造与排障实战
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
本文基于 Nx 仓库中面向 LLM/Agent 执行的 Vite 8 迁移指令(ai-instructions-for-vite-8.md)编写,并对照仓库内实际迁移代码与测试进行源码级印证。本文面向正在 Nx 单仓(monorepo)中从 Vite 7 升级到 Vite 8 的开发者,读完你将掌握:
rollupOptions到rolldownOptions的自动与手工改造、@vitejs/plugin-reactv6(Oxc 取代 Babel)的适配、Angular + Vitest 的@oxc-project/runtime依赖补齐、moduleResolution类型解析修复,以及一整套可落地的迁移后验证与回退(固定 Vite 7)策略。
迁移背景:Vite 8 用 Rolldown 取代 Rollup
Vite 8 最核心的底层变化是把生产构建器从 Rollup 换成 Rolldown。Rolldown 用 Rust 重写了打包核心,API 层面尽力兼容 Rollup,但并非逐字节等价:同一个输入下,Rolldown 生成的 chunk 数量、module 划分与 Rollup 存在差异;少数选项的语义也发生了变化(例如output.manualChunks在 Rolldown 中只接受函数形式,对象形式的 glob 映射不再合法)。同时 Vite 8 更新了一批插件 API,其中对多数 React 项目影响最大的是@vitejs/plugin-reactv6 弃用 Babel。
在 Nx 仓库中,这一升级被登记为 Nx 23.0.0 的迁移。查看 packages/vite/migrations.json 可以看到23.0.0版本条目的包更新规则:
- 要求
vite >=7.0.0 <8.0.0时触发,将vite升级到^8.0.0; - 同时把
@vitejs/plugin-react升级到^6.0.0; - 与
@remix-run/dev标记为incompatibleWith(即该场景下需人工确认兼容性)。
配套的迁移产物包括两个迁移:rename-rollup-options-to-rolldown-options(自动执行的重命名 codemod)和create-ai-instructions-for-vite-8(把本文所述的迁移指令注入到迁移流程,供 LLM/Agent 按步骤执行),两者都要求vite >= 8.0.0才触发。仓库内@nx/vite的 peerDependencies 同时声明支持^5 || ^6 || ^7 || ^8(见 packages/vite/package.json),说明插件层对多版本是兼容的,破坏面主要集中在用户侧配置。
迁移前清单
动手之前,先完成三件事:
盘点所有使用 Vite 的项目,用 Nx 的可发现性命令列出带
build、serve目标的项目:nx show projects --with-target build nx show projects --with-target serve定位所有 Vite 配置文件:
- 全局搜索
vite.config.{ts,js,mts,mjs,cts,cjs}; - 检查
project.json中是否内联了与 Vite 相关的选项。
提示:Nx 的
@nx/vite/plugin正是通过**/vite.config.{js,ts,mjs,mts,cjs,cts}这个 glob 自动推断项目的(见 packages/vite/src/plugins/plugin.ts),因此配置文件名必须符合该模式才会被识别。- 全局搜索
检查 Cypress 组件测试版本:Cypress >= 15.14.0 才支持 Vite 8。
nx migrate会自动把 Cypress 升到该版本;如果你在package.json里显式把 Cypress 钉在了 15.14.0 以下,需要在升级 Vite 之前先解除钉版。
迁移步骤分类
1. 把rollupOptions重命名为rolldownOptions
nx migrate提供的 codemod 会自动处理vite.config.{ts,js,mts,mjs,cts,cjs}文件中的重命名。如果你在其他地方(比如配置文件导入的 helper 模块、共享配置构建函数)也声明了rollupOptions,需要手工重命名。
搜索模式:在任意 TypeScript/JavaScript 文件中搜索rollupOptions。
// ❌ BEFORE (Vite 7) export default defineConfig({ build: { rollupOptions: { external: ['react'], output: { manualChunks: { vendor: ['react', 'react-dom'] } }, }, }, }); // ✅ AFTER (Vite 8) export default defineConfig({ build: { rolldownOptions: { external: ['react'], output: { manualChunks: { vendor: ['react', 'react-dom'] } }, }, }, });codemod 的源码实现与边界(想理解自动迁移能做到什么程度,值得读一读 rename-rollup-options-to-rolldown-options.ts):
- 它使用
@phenomnomnominal/tsquery的 AST 选择器PropertyAssignment > :matches(Identifier[name=rollupOptions], StringLiteral[value=rollupOptions])匹配属性键,因此既能命中裸键形式rollupOptions:,也能命中 JSON 风格带引号的'rollupOptions':形式;对带引号的键会保留原有引号风格。 - 只处理匹配
**/vite.*config*.{js,ts,mjs,mts,cjs,cts}的文件,且对不含rollupOptions的文件直接跳过;多次运行是幂等的(已有rolldownOptions不会重复改写)。这些行为都有对应的单元测试覆盖,见 rename-rollup-options-to-rolldown-options.spec.ts。 - 重命名同时覆盖顶层
build和嵌套的environments.<env>.build两种位置(对应environmentsAPI 下的 client/ssr 等多环境构建),示例见 rename-rollup-options-to-rolldown-options.md。
Action Items:
- 验证 codemod 覆盖了每个配置文件(在 vite 配置内
rg "rollupOptions"应返回零命中) - 手工重命名 helper 模块或共享配置构建器中的
rollupOptions - 更新解析
build.rollupOptions的 CI 脚本(例如自定义的 bundle 体积断言)
注意:Vite 8 仍把
rollupOptions当作已废弃别名接受(会复制值到rolldownOptions并打印弃用警告),但在同一层级同时混用两者可能引发优先级歧义——rolldownOptions优先。
2.@vitejs/plugin-reactv6:Oxc 取代 Babel
Vite 8 要求@vitejs/plugin-react@^6,该版本用 Oxc 替代 Babel 完成 JSX 转换,插件的babel选项已被移除。
// ❌ BEFORE (Vite 7, plugin-react v4) import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [ react({ babel: { plugins: ['babel-plugin-styled-components'], }, }), ], }); // ✅ AFTER (Vite 8, plugin-react v6) import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], });Action Items:
- 移除
react()调用中的babel选项 - 如果你依赖某个 Babel 插件(如 styled-components、emotion、relay),寻找 Oxc 兼容的替代品,或改用
@vitejs/plugin-react-swc(同样不依赖 Babel)。任意 Babel 插件并没有现成的即插即用替代方案 - 如果无法放弃 Babel 插件,暂时留在 Vite 7 + plugin-react v4(见下文「项目级 Vite 7 固定」)
- 运行
pnpm install(或你所用包管理器等价命令)让新版本 plugin-react 完成解析
从仓库线索看,Nx 的 Vite 配置生成器在生成 React 项目配置时会根据场景在
@vitejs/plugin-react与@vitejs/plugin-react-swc之间做选择(见 packages/vite/src/generators/configuration/configuration.ts),这印证了 SWC 路径是官方认可的无 Babel 替代方向。
3. Angular + Vitest(vitest-analog 路径):补充@oxc-project/runtime
如果 Angular 项目的test目标用的是@nx/vitest:test(即由@analogjs/vite-plugin-angular接线的 vitest-analog 配置),需要在工作区根devDependencies中显式声明@oxc-project/runtime。
为什么会缺这个依赖:@analogjs/vite-plugin-angular注册的angularVitestPlugin(仅在测试模式下激活)的transform钩子会匹配包含async的@angular/*fesm2022模块(以及任意@angular/cdk文件),并调用vite.transformWithOxc(code, id, { target: 'es2016', … })。这个故意降级是为了配合 Zone.js:fakeAsync等工具依赖对 promise 调度的 monkey-patch,无法拦截原生async/await,所以插件把它们降级成 Zone.js 可以拦截的形式。在target: 'es2016'下,oxc 把辅助函数作为外部@oxc-project/runtime/helpers/*导入发出(oxc 默认HelperMode = 'Runtime')。而上游链路(analogjs、@angular/core、vite、rolldown)都没有以消费方工作区可解析的方式声明@oxc-project/runtime,因此vite:import-analysis在缺少该依赖时无法解析这些导入。
不需要此依赖的情况:test目标使用@angular/build:unit-test或@nx/angular:unit-test(vitest-angular 路径)的 Angular 项目——该路径不加载@analogjs/vite-plugin-angular,设置了optimizeDeps.noDiscovery: true,并使用内存中的测试提供器,因此产生 helper 导入的降级转换根本不会运行。
搜索模式:test.executor为@nx/vitest:test且vite.config.*中引入@analogjs/vite-plugin-angular的项目。
rg '"@nx/vitest:test"' --type json rg '@analogjs/vite-plugin-angular' --type ts --type jsAction Items:
- 对每个受影响的工作区,把
@oxc-project/runtime加到根devDependencies(Nx 的 Angular 生成器在 vitest-analog 路径上会自动添加;请检查早于该行为的老工作区) - 运行
pnpm install(或等价命令) - 运行项目测试,确认 helper 能解析
背景知识:
@nx/vite:test执行器与@nx/vite包中的 vitest 能力已在 Nx 23 移除,vitest 支持统一由@nx/vitest提供;Nx 23 的自动迁移会兜底完成@nx/vite:test→@nx/vitest:test的切换,详见 ensure-vitest-package-migration.md。
4.moduleResolution: "node"下的类型解析
Vite 8 的类型只通过条件exports提供(删除了 Vite 7 时代顶层types字段),TypeScript 在moduleResolution: "node"下无法解析,典型症状是defineConfig、UserConfig或插件返回类型报错。
Action Items:
- 更新受影响的
tsconfig*.json:"moduleResolution": "bundler"(推荐)或"node16"/"nodenext" - 如果无法修改
moduleResolution,用 vite 导入处的显式as any收窄影响范围(Nx 生成的配置在个别位置已这样做) - 修改后运行
tsc --noEmit确认类型干净解析
5. 打包校验脚本重新定基线
Rolldown 与 Rollup 对同一输入产生的 chunk 数、module 数与 Rollup 不同。自定义的构建校验(例如“bundle 恰好有 N 个 chunk”)需要重新定基线。
Action Items:
- 找出断言 chunk/module 数量或名称的脚本
- 重新构建并更新期望值
- 今后优先断言体积预算(size budget)而非精确计数
6. 项目级 Vite 7 固定(存在自定义 Babel 插件时)
如果某个项目依赖没有 Oxc 等价物的 Babel 插件,把该项目钉在 Vite 7:
// package.json (workspace root) { "devDependencies": { "vite": "^7.1.0", "@vitejs/plugin-react": "^4.3.0", }, }如果只有部分项目需要留在 7、其余升到 8,使用包管理器的 overrides 机制:
- pnpm:根
package.json的pnpm.overrides - npm/yarn:
overrides/resolutions
Action Items:
- 记录哪些项目钉在 Vite 7 及原因
- 跟进 Oxc 插件等价物,以便日后解除钉版
迁移后验证
逐项目跑测试:
nx run-many -t test -p PROJECT_NAME构建所有受影响项目:
nx affected -t build验证开发服务器:
nx serve PROJECT_NAME打开应用,确认源码改动后 HMR 依然生效。
验证 CI 流水线:
nx prepush对照迁移清单复核:
- 所有
rollupOptions引用已重命名为rolldownOptions @vitejs/plugin-react已升级到 v6(或钉在 v4 且记录原因)react()调用中不再残留babel: { ... }选项(或对应项目已钉在 Vite 7)- Angular + vitest-analog 项目已在根
devDependencies声明@oxc-project/runtime - Cypress 已由
nx migrate升级(>= 15.14.0 以支持 Vite 8) - 所有受影响项目
tsc --noEmit通过 - 构建、测试、开发服务器命令全部成功
补充说明:Nx 的 Vite build 执行器会通过动态加载(
loadViteDynamicImport)引入 Vite 的build/resolveConfig/mergeConfig等 API,并用mergeConfig把 Nx 侧解析出的root、configFile、build.outDir与项目自身配置合并后交给 Vite 构建(见 packages/vite/src/executors/build/build.impl.ts)。这也意味着迁移是否成功最终以nx build的真实输出为准,而不是只看配置语法正确。
常见问题与解决
Issue:Cannot find name 'rollupOptions'或构建选项被忽略
Solution:重命名为rolldownOptions。Vite 8 仍把rollupOptions作为废弃别名接受(会把值复制到rolldownOptions并打印弃用警告),但同一层级混用两者可能产生优先级意外(rolldownOptions优先)。
Issue:Babel 插件不再生效(例如 styled-components 的 className 丢失)
Solution:@vitejs/plugin-react@6移除了 Babel。寻找 Oxc 兼容替代、改用@vitejs/plugin-react-swc,或钉在 Vite 7 + plugin-react v4。
Issue:Angular + Vitest 报Failed to resolve import "@oxc-project/runtime/helpers/..."
Solution:把@oxc-project/runtime加到根devDependencies并重新安装。这只影响test目标使用@nx/vitest:test(vitest-analog 配置)的项目;使用@angular/build:unit-test/@nx/angular:unit-test的项目不受影响。
Issue:从 vite 导入defineConfig、UserConfig或Plugin时出现类型错误
Solution:在 tsconfig 中设置moduleResolution: "bundler"(需要 Node 风格解析时用nodenext)。
Issue:Cypress 组件测试在 Vite 8 下无法启动
Solution:确认安装的是cypress >= 15.14.0(Vite 8 支持在该版本落地)。nx migrate会自动升级 Cypress;如果你在package.json中把它钉低了,移除钉版并重新安装。
Issue:升级后 bundle 体积或 chunk 数量断言失败
Solution:Rolldown 的 chunk 划分方式与 Rollup 不同,重新定基线期望值即可。
待审查文件清单
# Vite 配置文件 find . -name "vite.config.*" -not -path "*/node_modules/*" # Cypress 组件测试配置 rg "@nx/(angular|react|next|remix)/plugins/component-testing" # plugin-react 中的 Babel 插件用法 rg "@vitejs/plugin-react.*babel|babel:\s*\{" --type ts --type js # 使用 Vitest 的 Angular 项目 rg "@angular/build" -l package.json护栏:迁移红线
不要:
- 通过删除断言或把断言替换为
expect(true).toBe(true)来强行让测试通过; - 未找到等价方案就擅自剥离
react()插件选项; - 迁移后把 Cypress 回滚到 15.14.0 以下——旧版 Cypress 在 Vite 8 下无法启动。
面向 LLM/Agent 的执行纪律
按迁移指令执行时(这本身是 Nx 注入给 LLM 的执行文档,见 ai-instructions-for-vite-8.md):
- 系统化推进:完成一个类别再进入下一个类别;
- 每步改后即测:每个步骤完成后构建并测试受影响的项目;
- 向用户汇报:说明哪些类别适用、哪些被跳过;
- 使用待办工具跟踪:让迁移进度可见;
- 遇到无 Oxc 等价物的 Babel 插件依赖时停下询问:固定 Vite 7 属于工作区层面的决策,不应擅自替用户拍板。
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考