首页
/ Vite 8 迁移实战指南:从 esbuild/Rollup 切换至 Rolldown + Oxc 工具链的完整拆解

Vite 8 迁移实战指南:从 esbuild/Rollup 切换至 Rolldown + Oxc 工具链的完整拆解

2026-09-06 16:59:53作者:房伟宁

本文基于 Vite 仓库官方的 Vite 8 迁移指南 编写,系统梳理从 Vite 7 升级到 Vite 8 的每一步变更:底层打包器由 esbuild + Rollup 全面替换为 Rolldown + Oxc 之后的配置迁移路径、自动兼容层的行为边界,以及 CJS 互操作性、压缩、模块类型检测等十余项破坏性变更。读完后,你可以判断自己项目命中的兼容性风险点,并逐项完成 esbuildOptionsesbuildbuild.rollupOptions 等废弃配置的替换。

迁移背景:Vite 8 用了一套全新的工具链

Vite 8 最核心的变化是:构建与转换不再依赖 esbuild 和 Rollup,而是切换到 Rolldown 与 Oxc 生态。这直接带来三类影响:

  1. 配置层:一批 esbuild*build.rollupOptions 相关的配置项被废弃,但 Vite 保留了自动转换的兼容层,老配置仍可运行(会打印废弃警告);
  2. 行为层:默认浏览器目标版本、CommonJS 互操作规则、require 外部模块的处理方式等发生了变化,部分代码可能需要适配;
  3. 能力层:少数 Rollup 特性(如 system/amd 输出格式、部分插件钩子)因 Rolldown 尚未支持而被移除。

仓库中的实现与这份文档严格对应,例如兼容转换逻辑位于 oxc 配置转换函数依赖优化选项兼容层,构建默认值位于 build.ts

另外有两类读者需要特别注意:

  • rolldown-vite(Rolldown 集成的技术预览版)迁移的用户:该包已经实现了 Rolldown 集成,因此本指南中标注 “NRV”(Not Relevant for Vite,对 rolldown-vite 用户不适用)的章节无需关注,只需阅读其余部分;
  • 从 Vite 6 迁移的用户:请先阅读 Vite 7 文档中的 v6 迁移指南完成 Vite 7 升级,再按本文完成 Vite 8 的变更。

默认浏览器目标版本上调

build.target'baseline-widely-available' 的默认浏览器版本统一上调:

浏览器 旧默认值 新默认值
Chrome 107 111
Edge 107 111
Firefox 104 114
Safari 16.0 16.4

这些版本与 Baseline Widely Available 特性集在 2026-01-01 的基线对齐,即对应浏览器版本均已发布约两年半。如果你的项目需要覆盖更老的浏览器,请显式配置 build.target;反之,如果你的最低目标版本高于旧默认值,升级 Vite 8 后输出的语法可能更现代,体积或兼容性行为会有细微差异。

渐进式迁移:用 rolldown-vite 做中间站

rolldown-vite 包实现了 “Rolldown + Vite 7 其余行为不变” 的组合,可以作为从 Vite 7 到 Vite 8 的中间步骤:先在 Vite 7 上切换到 rolldown-vite(参考 Vite 7 文档的 Rolldown 集成指南),确认打包行为无回归后,再正式升级到 Vite 8。

对于已经从 rolldown-vite 迁移过来的用户,只需还原 package.json 中的依赖别名并升级到 Vite 8:

{
  "devDependencies": {
    "vite": "npm:rolldown-vite@7.2.2" // 旧
    "vite": "^8.0.0" // 新
  }
}

依赖预构建(Optimizer)改用 Rolldown

依赖优化器不再使用 esbuild,而是使用 Rolldown。Vite 仍支持 optimizeDeps.esbuildOptions 以保持向后兼容:它会被自动转换为 optimizeDeps.rolldownOptions,但该选项已废弃,未来会移除。

自动转换的选项映射如下(详见 依赖优化配置参考):

esbuildOptions rolldownOptions
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(部分支持)

从源码可以确认这一兼容层的行为边界:config.ts 中的转换逻辑 在检测到 optimizeDeps.esbuildOptions 非空时会先打印废弃警告,然后按 “仅在对应 rolldownOptions 字段未显式设置时” 逐一填充;loader 的转换会跳过 copycssdefaultfilelocal-css 这些无法映射的取值。源码注释还明确列出了一批不会被转换的选项(legalCommentstargetsupportedjsx* 系列、mangleProps 等),这意味着如果这些选项曾在依赖优化中生效,需要手动确认新的等效配置。

你可以在 configResolved 钩子中查看兼容层最终设置的选项,验证转换结果:

const plugin = {
  name: 'log-config',
  configResolved(config) {
    console.log('options', config.optimizeDeps.rolldownOptions)
  },
}

建议尽快将配置迁移到 optimizeDeps.rolldownOptions

JavaScript 转换:Oxc 接管 esbuild transform

JavaScript 转换(TypeScript/JSX 编译)由 Oxc 完成。esbuild 配置项同样保留兼容层,会被自动转换为 oxc 配置esbuild 选项已废弃,未来会移除。

自动转换的映射关系:

  • esbuild.jsxInject -> oxc.jsxInject
  • esbuild.include -> oxc.include
  • esbuild.exclude -> oxc.exclude
  • esbuild.jsx -> oxc.jsx
    • 'preserve' -> 'preserve'
    • 'automatic' -> { runtime: 'automatic' },其中 esbuild.jsxImportSource -> oxc.jsx.importSource
    • 'transform' -> { runtime: 'classic' },其中 esbuild.jsxFactory -> oxc.jsx.pragmaesbuild.jsxFragment -> oxc.jsx.pragmaFrag
  • esbuild.jsxDev -> oxc.jsx.development
  • esbuild.jsxSideEffects -> oxc.jsx.pure
  • esbuild.define -> oxc.define
  • esbuild.banner / esbuild.footer -> 需改用自定义插件的 transform 钩子实现

这个转换在仓库中的实现是 convertEsbuildConfigToOxcConfigjsx 三种取值分别映射到 Oxc 的 runtime 模式,jsxSideEffects 取反后写入 pure,而 banner/footer 会打印“该选项已废弃,请用带 transform 钩子的插件替代”的警告(对应 esbuildBannerFooterCompatPlugin 的兼容实现)。新配置项的用法可参考 oxc 配置参考

两个需要注意的转换限制

  1. esbuild.supported 不受 Oxc 支持。该选项用于控制 esbuild 是否降级某些浏览器原生特性;从源码结构看,Oxc 转换路径中没有对应能力,如果你的项目依赖它做了低版本浏览器降级,需要单独评估替代方案。
  2. Oxc Transformer 暂不支持原生装饰器(native decorators)降级,正在等待规范进展。如果项目使用了需要在旧环境中降级的原生装饰器,官方文档给出了两套临时方案:

方案一:Babel

# npm / yarn / pnpm / bun 等价:
npm install -D @rolldown/plugin-babel @babel/plugin-proposal-decorators
// vite.config.ts
import { defineConfig } from 'vite'
import babel from '@rolldown/plugin-babel'

function decoratorPreset(options: Record<string, unknown>) {
  return {
    preset: () => ({
      plugins: [['@babel/plugin-proposal-decorators', options]],
    }),
    rolldown: {
      // 仅当文件包含装饰器时才运行该转换
      filter: {
        code: '@',
      },
    },
  }
}

export default defineConfig({
  plugins: [babel({ presets: [decoratorPreset({ version: '2023-11' })] })],
})

方案二:SWC

npm install -D @rollup/plugin-swc @swc/core
import { defineConfig, withFilter } from 'vite'
import swc from '@rollup/plugin-swc'

export default defineConfig({
  // ...
  plugins: [
    withFilter(
      swc({
        swc: {
          jsc: {
            parser: { decorators: true, decoratorsBeforeExport: true },
            transform: { decoratorVersion: '2023-11' },
          },
        },
      }),
      // 仅当文件包含装饰器时才运行该转换
      { transform: { code: '@' } },
    ),
  ],
})

esbuild 降级为可选依赖

Vite 8 中 esbuild 不再被直接使用,降级为可选依赖。如果你的插件使用了 transformWithEsbuild 函数,需要自己将 esbuild 安装为 devDependency;且 transformWithEsbuild 已废弃,推荐迁移到新的 transformWithOxc。后者的实现位于 transformWithOxc 函数,基于 rolldown/utilstransformSync 完成转换并附带 source map 与 tsconfig 解析缓存。

你可以通过 configResolved 查看兼容层写入的 oxc 选项:

const plugin = {
  name: 'log-config',
  configResolved(config) {
    console.log('options', config.oxc)
  },
}

JavaScript 压缩:Oxc Minifier 成为默认

JavaScript 压缩默认由 Oxc Minifier 完成。在 build.ts 中可以看到客户端构建的默认值:minifytrue 时解析为 'oxc',服务端与 bundler 模式开发下则关闭压缩。

迁移要点:

  • 回退到 esbuild:设置废弃选项 build.minify: 'esbuild' 可切回 esbuild 压缩,此时构建阶段会额外挂载 esbuild 插件(见 build.ts 中 minify 为 esbuild 时的 post 插件),并且必须自行安装 esbuilddevDependency,因为 Vite 已不直接依赖 esbuild。
  • 选项迁移:原来用 esbuild.minify* 系列控制压缩行为的,改用 build.rolldownOptions.output.minify;原来用 esbuild.drop 删除 console/debugger 的,改用 Oxc Minifier 的 output.minify.compress.drop* 系列选项。
  • 属性混淆(mangling)不支持manglePropsreservePropsmangleQuotedmangleCache 在 Oxc 中没有对应能力;如有需求,可关注 Oxc 项目的对应 issue 进展。
  • 压缩假设差异:esbuild 与 Oxc Minifier 对源码的假设略有不同,若怀疑压缩导致线上问题,建议对照两者的 minify assumptions 文档逐一排查,并向上游报告与压缩相关的缺陷。

CSS 压缩:Lightning CSS 成为默认

CSS 压缩默认改用 Lightning CSS。在 build.ts 中的默认值逻辑 可以看到:服务端环境固定使用 'lightningcss',客户端环境在 build.minify 开启时跟随启用。

  • 回退到 esbuild:设置 build.cssMinify: 'esbuild',同样需要自行安装 esbuilddevDependency
  • 体积影响:Lightning CSS 支持更好的语法降级(syntax lowering),CSS 包体积可能略微增大,这是功能增强带来的正常现象,升级后可对比构建产物体积确认影响范围。

CommonJS 互操作性:default 导入规则统一

这是升级 Vite 8 时最容易踩坑的行为变更。从 CommonJS 模块的 default 导入现在按统一规则处理:满足以下任一条件时,default 即被导入 CJS 模块的 module.exports 值;否则取 module.exports.default

  1. 导入方(importer)是 .mjs.mts 文件;
  2. 导入方最近的 package.jsontype 字段为 module
  3. 被导入 CJS 模块的 module.exports.__esModule 未设置为 true

旧行为对照

变更前,开发与构建两套逻辑并不一致:

  • 开发环境旧规则:导入方被依赖优化且为 .mjs/.mts,或导入方被依赖优化且其最近 package.jsontypemodule,或 __esModule 不为 true 时,取 module.exports;否则取 module.exports.default
  • 构建旧规则(Rollup commonjs 插件 defaultIsModuleExports 为默认 'auto' 时):__esModule 不为 true module.exports 上不存在 default 属性时,取 module.exports;否则取 module.exports.default

可以看到新规则消除了 “构建时靠 default 属性是否存在兜底” 的模糊分支,开发和构建行为一致。这个变更可能破坏依赖旧规则的 CJS 包导入。仓库中保留了回退开关:legacy 配置类型定义 中的 inconsistentCjsInterop,其 JSDoc 明确说明它用于 “opt-in 到 Vite 8 之前不一致的 CJS 互操作行为”,并在 importAnalysis 插件 中参与开发期的互操作判定,同时经 插件索引 传递给构建侧。

如果某个三方包因该变更出问题,官方建议是向该包作者反馈或提交 PR(并附上 Rolldown 关于 CJS 打包的文档背景),而不是长期依赖回退开关:

export default defineConfig({
  legacy: {
    inconsistentCjsInterop: true, // 临时恢复旧行为
  },
})

模块解析与外部模块行为变更

移除基于格式嗅探的模块解析

过去当 package.json 同时存在 browsermodule 字段时,Vite 会根据文件内容嗅探格式、为浏览器挑选 ESM 文件。这个启发式源于部分包用 module 指向 Node 侧 ESM、用 browser 指向 UMD 的历史包袱。如今标准的 exports 字段已广泛采用,Vite 8 移除了该启发式,始终按 resolve.mainFields 的字段顺序解析。

如果项目依赖了旧行为,两种替代方案:用 resolve.alias 把目标字段映射到期望的文件,或用包管理器的补丁机制(如 patch-packagepnpm patch)修改该包的 package.json

外部化模块的 require 调用保持原样

对外部化模块的 require 调用现在保留为 require 调用,不再转换为 import 语句,以保留 require 的语义。若你确实希望转换,可使用 Rolldown 内置的 esmExternalRequirePlugin,它从 vite 直接再导出(见 node/index.ts):

import { defineConfig, esmExternalRequirePlugin } from 'vite'

export default defineConfig({
  // ...
  plugins: [
    esmExternalRequirePlugin({
      external: ['react', 'vue', /^node:/],
    }),
  ],
})

UMD / IIFE 输出中 import.meta.url 不再填充

import.meta.url 在 UMD / IIFE 输出格式中不再被 polyfill,默认被替换为 undefined。如需恢复旧行为,可以组合 define 选项与 build.rolldownOptions.output.intro 来实现。

移除 build.rollupOptions.watch.chokidar

该选项已删除,请迁移到 build.rolldownOptions.watch.watcher

manualChunks 对象形式移除、函数形式废弃

对象形式的 output.manualChunks 不再支持;函数形式已废弃。Rolldown 提供了更灵活的 codeSplitting 选项替代,详见 Rolldown 的 Manual Code Splitting 文档。

build() 抛出 BundleError

此变更仅影响 JS API 用户。

通过 build() 调用构建时,抛出的不再是插件内原始错误,而是 BundleError,其类型为 Error & { errors?: RolldownError[] },单个错误包裹在 errors 数组中。需要遍历具体错误时访问 .errors

try {
  await build()
} catch (e) {
  if (e.errors) {
    for (const error of e.errors) {
      console.log(error.code) // 错误码
    }
  }
}

模块类型自动检测(影响插件作者)

Rolldown 支持按解析到的 id 扩展名自动设置模块类型(module types,类似 esbuild 的 loader 选项)。如果你在 loadtransform 钩子中把其他模块类型的内容转换成 JavaScript,可能需要在返回值中显式加上 moduleType: 'js'

const plugin = {
  name: 'txt-loader',
  load(id) {
    if (id.endsWith('.txt')) {
      const content = fs.readFile(id, 'utf-8')
      return {
        code: `export default ${JSON.stringify(content)}`,
        moduleType: 'js', // 需要显式声明
      }
    }
  },
}

Vite 自身的 Oxc 插件就遵循这一约定:oxc 插件的 transform 返回值 中显式携带 moduleType: 'js'

其他相关废弃项

以下选项已废弃,未来会移除:

  • build.rollupOptions:更名为 build.rolldownOptions(在 build.ts 中可见 rollupOptions 标记为 @deprecated 的别名);
  • worker.rollupOptions:更名为 worker.rolldownOptions
  • build.commonjsOptions:现为 no-op(空操作);
  • build.dynamicImportVarsOptions.warnOnError:现为 no-op;
  • resolve.alias[].customResolver:请改用带 resolveId 钩子且 enforce: 'pre' 的自定义插件替代。

已移除的废弃特性

  • import.meta.hot.accept 传递 URL 不再受支持,请改为传递模块 id。

进阶:少数场景会受影响的破坏性变更

以下变更预计只影响少数使用场景,按需核对:

  • Extglobs 不支持:picomatch 的 extglobs 语法在依赖优化等 glob 匹配场景暂不支持;
  • TypeScript 传统 namespace 仅部分支持:需对照 Oxc Transformer 的 TypeScript 文档确认你的用法是否在支持范围内;
  • define 不再共享对象引用:向 define 传入对象值时,每个被替换的变量会持有对象的独立副本(Oxc 的 define 行为),依赖“多处引用同一对象”的逻辑会失效;
  • bundle 对象行为变化(传入 generateBundle / writeBundle 钩子、由 build 返回的对象):
    • 不支持 bundle[foo] = ... 赋值,请用 this.emitFile() 代替(Rollup 本身也不推荐赋值);
    • 各钩子间共享的不再是同一引用;
    • structuredClone(bundle) 会抛出 DataCloneError,需改为 structuredClone({ ...bundle })
  • Rollup 的 parallel 钩子均按顺序执行:依赖钩子并行执行的插件行为会变慢或顺序敏感;
  • "use strict"; 注入策略不同:某些情况下不再注入,需对照 Rolldown 的 directives 文档评估;
  • plugin-legacy 不支持降级到 ES5 及以下:Vite 仓库内置的 plugin-legacy 包 在此约束下工作,需要 ES5 输出的老项目需另寻方案;
  • build.target 传入同一浏览器的多个版本会报错:esbuild 会静默选取最新版本,Vite 8 选择直接报错提示意图不清;
  • Rolldown 尚未支持、Vite 也随之移除的特性
    • build.rollupOptions.output.format: 'system'
    • build.rollupOptions.output.format: 'amd'
    • shouldTransformCachedModule 钩子
    • resolveImportMeta 钩子
    • renderDynamicImport 钩子
    • resolveFileUrl 钩子
  • parseAst / parseAstAsync 废弃:改用功能更完整的 parseSync / parse。仓库中 node/index.ts 可见 parseAstparseAstAsync 均标记 @deprecated 并指向 parseSync
  • 注释移除时机变化:注释现在在 renderChunk 钩子之前被移除(Rollup 是在之后);且除 Rolldown OutputOptions.comments 所列之外的注释会被移动,而 Rollup 只在相邻代码被移除时才删除注释——依赖注释存在的插件(如版权头处理)需要适配。

迁移落地清单

结合上述内容,一份可执行的升级检查单如下:

  1. 确认起点:在 Vite 6 上先完成 Vite 7 升级;在 Vite 7 上可先切 rolldown-vite 过渡;
  2. 检查 build.target:默认浏览器目标已上调(Chrome/Edge 111、Firefox 114、Safari 16.4),需要覆盖旧浏览器时显式指定;
  3. 替换废弃配置optimizeDeps.esbuildOptionsoptimizeDeps.rolldownOptionsesbuildoxcbuild.rollupOptions / worker.rollupOptions*.rolldownOptions;用 configResolved 钩子打印最终选项验证兼容层转换结果;
  4. 审计 CJS 依赖:重点验证从 CJS 包做 default 导入的代码路径,必要时临时开启 legacy.inconsistentCjsInterop: true,并向包作者反馈;
  5. 确认压缩配置:JS 默认 Oxc Minifier、CSS 默认 Lightning CSS;若需回退 esbuild,安装 esbuild 为 devDependency 并设置 build.minify: 'esbuild' / build.cssMinify: 'esbuild'
  6. 排查特殊场景banner/footer、装饰器降级、system/amd 格式、manualChunks 对象形式、import.meta.hot.accept 传 URL、parseAst 调用、依赖钩子并行执行的插件;
  7. 清理兼容开关:迁移稳定后移除 legacy.inconsistentCjsInterop 等临时开关,回归 Rolldown 的标准行为。

以上所有行为均以当前仓库实现为准:配置兼容层见 config.tsoxc.ts,构建默认值与回退插件挂载见 build.ts,导出面变更见 node/index.ts。升级后如遇与压缩、CJS 互操作相关的回归,建议优先对照 Rolldown 与 Oxc 官方文档中的 assumptions 说明定位差异来源。

登录后查看全文
热门项目推荐
相关项目推荐