Vite 8 升级核对清单:CJS default 导入和 browser/module 嗅探会先坏
信源是 Vite 官方《Migration from v7》(vite.dev/guide/migration,当前文档版本 v8.3.4)。Vite 8 用 Rolldown + Oxc 取代 esbuild + Rollup,官方基准给出的构建提速在 10–30x 量级;但升级真正花时间的是语义变化。下面按「哪类项目会先坏」排序,不按文档目录。
前提条件先确认:Node 要求 20.19+ / 22.12+(与 Vite 7 相同)。build.target 默认值随 'baseline-widely-available' 抬高到 chrome111 / edge111 / firefox114 / safari16.4,对齐 2026-01-01 的 Baseline Widely Available,都是约两年半前发布的浏览器。如果项目的 target 是从 Chrome 107 一路兼容上来的,产物会直接变。
1. 导入 CommonJS 包的项目:default 语义统一
这一类最先坏,而且坏得最隐蔽:以前「dev 正常、build 坏」的差异,现在会变成两边一致地坏或一致地好。
CJS 的 default 导入现在只有一套规则。满足任一条件时,default 就是 module.exports:
- 导入方是
.mjs或.mts - 导入方最近的
package.json的type为module - 被导入 CJS 的
module.exports.__esModule不为true
否则 default 是 module.exports.default。
旧行为分两套:dev 下上述条件只对「被纳入依赖优化」的导入方成立;build 下走的是另一套判断——__esModule 不为 true 且 module.exports 没有 default 属性(假设 build.commonjsOptions.defaultIsModuleExports 为默认的 'auto')。所以那些只在某一种模式下成立的写法,会开始报 undefined is not a function。
- 用
import pkg from 'cjs-pkg'的地方逐个确认拿到的到底是module.exports还是module.exports.default - 临时逃生口:
legacy.inconsistentCjsInterop: true(已弃用),恢复旧的不一致行为 - 同时向包作者反馈,参考 Rolldown 的「Ambiguous default import from CJS modules」文档
2. 依赖同时声明 browser 和 module 字段:内容嗅探没了
package.json 里同时有 browser 和 module 时,Vite 过去会按文件内容判断格式,并且倾向给浏览器挑 ESM 文件。现在这个启发式被移除,始终严格按 resolve.mainFields 的顺序解析。
症状是某些依赖在浏览器里解析到了 CJS 版本,或直接解析失败。
- 先看这些包的
mainFields结果是否符合预期 - 绕过用
resolve.alias直接映射到具体文件 - 或者用
patch-package/pnpm patch给依赖打补丁
3. 用了 manualChunks 或 watch.chokidar 的构建配置:直接失效
output.manualChunks的对象形式已被移除,函数形式弃用;改用 Rolldown 的codeSplittingbuild.rollupOptions.watch.chokidar移除,改用build.rolldownOptions.watch.watcher
这两项属于配置层面直接报错或静默不生效,打包分块策略会整体变形,需要重新核对产物体积。
4. 配置改名与自动转换:能自动转的,和转不了的
大部分旧字段仍能用,但都已弃用并自动转换。核对方式是在 configResolved 里打印转换结果:
export default {
plugins: [{
name: 'print-config',
configResolved(config) {
console.log(config.optimizeDeps.rolldownOptions)
},
}],
}
改名映射:
| 旧 | 新 |
|---|---|
build.rollupOptions | build.rolldownOptions |
worker.rollupOptions | worker.rolldownOptions |
optimizeDeps.esbuildOptions | optimizeDeps.rolldownOptions |
esbuild | oxc |
optimizeDeps.esbuildOptions 的自动转换:minify→output.minify、treeShaking→treeshake、define→transform.define、loader→moduleTypes、preserveSymlinks→!resolve.symlinks、resolveExtensions→resolve.extensions、mainFields→resolve.mainFields、conditions→resolve.conditionNames、keepNames→output.keepNames、platform→platform、plugins→plugins(部分支持)。
esbuild 选项转 Oxc 的对应关系:jsxInject→oxc.jsxInject、include/exclude 保留、jsx: 'preserve'→oxc.jsx: 'preserve'、'automatic'→{ runtime: 'automatic' }、jsxImportSource→oxc.jsx.importSource、'transform'→{ runtime: 'classic' }、jsxFactory→jsx.pragma、jsxFragment→jsx.pragmaFrag、jsxDev→jsx.development、jsxSideEffects→jsx.pure、define→oxc.define。
没有等价物、必须自己处理的几项:
esbuild.banner/esbuild.footer:要自己写一个在transformhook 里拼内容的插件esbuild.supported:Oxc 不支持(oxc-project/oxc#15373)- 原生装饰器无法降级(oxc-project/oxc#9170):需要
@rolldown/plugin-babel+@babel/plugin-proposal-decorators,或用@rollup/plugin-swc变通 - minify 的属性 mangle 系列(
mangleProps、reserveProps、mangleQuoted、mangleCache):Oxc 不支持(oxc-project/oxc#15375)
另外 esbuild 已不是 Vite 的直接依赖,变成可选依赖。用了 transformWithEsbuild 的插件要自己把 esbuild 装进 devDependencies;该函数已弃用,改用 transformWithOxc。
压缩相关:JS 压缩交给 Oxc Minifier,可用已弃用的 build.minify: 'esbuild' 回退(同样要自己装 esbuild)。esbuild.minify* 改为 build.rolldownOptions.output.minify,esbuild.drop 改为 output.minify.compress.drop*。CSS 压缩默认 Lightning CSS,可用 build.cssMinify: 'esbuild' 回退(需装 esbuild);Lightning CSS 的语法降级更好,CSS 体积可能略增。
5. 运行时行为变化:报错形态、HMR、import.meta.url
build()抛BundleError(只影响 JS API 用户)。类型是Error & { errors?: RolldownError[] },单个错误在errors数组里:
try {
await build()
} catch (e) {
if (e.errors) {
for (const error of e.errors) console.log(error.code)
}
}
import.meta.hot.accept不再接受 URL(NRV),要改传 id(rolldown-vite#21382)- UMD / IIFE 里的
import.meta.url不再 polyfill,默认替换为undefined;要恢复旧行为用define配合build.rolldownOptions.output.intro - 外部化模块的
require保持为require,不再转成import以保持语义。要转成import用 Rolldown 内置的esmExternalRequirePlugin(从vite再导出):
import { defineConfig, esmExternalRequirePlugin } from 'vite'
export default defineConfig({
plugins: [
esmExternalRequirePlugin({ external: ['react', 'vue', /^node:/] }),
],
})
6. 插件作者:hook 顺序、bundle 对象、moduleType
- Rollup 的并行 hook 在 Rolldown 里顺序执行,原来靠并发省时间的插件会变慢,也可能暴露隐式顺序依赖
bundle对象不能再给bundle[foo]赋值,改用this.emitFile();引用不跨 hook 共享structuredClone(bundle)会报DataCloneError,要写structuredClone({ ...bundle })(rolldown-vite#128)- 在
load/transform里把非 JS 内容转成 JS 时,返回值可能需要加moduleType: 'js'(Rolldown 按解析后 id 的扩展名自动设 module type) plugin-legacy不再支持降到 ES5 及以下(rolldown-vite#452)- Rolldown 不支持、Vite 也随之不支持:
output.format的'system'(rolldown#2387)与'amd'(rolldown#2528),以及shouldTransformCachedModule(rolldown#4389)、resolveImportMeta(rolldown#1010)、renderDynamicImport(rolldown#4532)、resolveFileUrl
7. 少数场景才会踩到的
- Extglobs 暂不支持(rolldown-vite#365)
- TypeScript legacy namespace 只部分支持
define传对象时不再共享引用,每个变量各自一份副本"use strict";有时不再注入- 给
build.target传同一浏览器的多个版本现在直接报错(esbuild 时期会挑最新版) - 注释在
renderChunk之前就被移除(Rollup 是之后);除列出的类型外其它注释会被移动,而 Rollup 只在相邻代码被移除时才删注释 parseAst/parseAstAsync弃用,改用parseSync/parse- 其它弃用:
build.commonjsOptions变成 no-op;build.dynamicImportVarsOptions.warnOnError变成 no-op;resolve.alias[].customResolver弃用,改用带resolveIdhook 且enforce: 'pre'的插件
8. 渐进迁移路径
rolldown-vite 包实现的是「Vite 7 + Rolldown」,不含 Vite 8 的其它改动,适合先在 Vite 7 上把 Rolldown 的问题隔离出来:
{ "vite": "npm:rolldown-vite@7.2.2" }
确认无问题后再升 8,改回:
{ "vite": "^8.0.0" }
如果是从 rolldown-vite 起跳,只有标题带 NRV 的小节适用(本文对应第 1 节的 HMR accept、build.target 默认值、移除的弃用特性)。
安装体积上,官方说明 Vite 8 比 Vite 7 大约多 15MB,其中 lightningcss 约 10MB、Rolldown 约 5MB。