跳转到正文

迁移规则 ​

This reference describes exactly what vp migrate does to a project: how it updates dependencies, rewrites source imports and package scripts, and adjusts package-manager configuration. See the migration guide for the command overview and workflow.

Except for Before You Migrate, which lists steps you take yourself, everything below describes automatic behavior.

迁移前 ​

  1. 运行 vp upgrade,以便全局 CLI 使用最新的迁移规则。过时的本地 vite-plus 并不会阻止迁移:当项目的本地副本版本较旧时,迁移会委托给全局 CLI。
  2. 在必要时将项目升级到 Vite 7+ 和 Vitest 4+。
  3. 从工作区根目录运行 vp migrate。在自动化环境中使用 --no-interactive。
  4. 检查每个发生变更的清单、包管理器配置、源码重写结果和生成的锁文件。
  5. 使用 vp install、vp check、vp test 和 vp build 进行验证。

迁移具有幂等性:成功迁移后再次运行,不应产生新的差异。

升级 vs. 完整设置 ​

对于已经依赖 vite-plus 的项目,vp migrate 只执行升级:它会更新依赖和包管理器配置,并完成导入。它不会修改项目设置。

  • --full 还会运行设置操作:Git hooks、编辑器配置、代理文件、ESLint、Prettier 和 tsup 迁移、框架 shim、tsconfig 的 baseUrl 修复,以及从 .nvmrc/Volta 到 .node-version 的转换。
  • --hooks、--agent 和 --editor 可在不使用 --full 的情况下选择单个设置操作。

Pack 配置 ​

vp migrate 会更新 vite.config.* 中的静态 pack 对象,以及 tsdown.config.* 中导出的对象,以适配 tsdown 0.23。对于已有的 Vite+ 项目,即使不使用 --full 也会执行此操作,包括工作区包。支持数组和由 defineConfig 回调返回的直接对象。JSON tsdown 配置会在合并到 vite.config.ts 后接收相同的更新。

之前的选项更新后的选项
bundle: falseunbundle: true
bundle: true移除;打包仍然是默认行为
outExtensionoutExtensions
publicDircopy
removeNodeProtocol: truenodeProtocol: 'strip'
injectStylecss.inject
inlineOnly / deps.onlyAllowBundledeps.onlyBundle
noExternaldeps.alwaysBundle
skipNodeModulesBundle: true / deps.skipNodeModulesBundle: truedeps.neverBundle: true
dts.tsgo / dts.oxc使用 dts.generator 选择;保留生成器选项对象并移除布尔标志
dts.cjsReexport移除;tsdown 会单独生成 CJS 声明
tsdown 或 vp pack 脚本中的 --public-dir--copy

当缺少 deps.resolveDepSubpath 时,迁移会将其设置为 true,以保留之前的默认行为。如果已启用 ATTW 检查且未设置 profile,则会添加 profile: 'strict'。显式值(包括 false)保持不变。

迁移会在最终安装依赖之前检查项目已安装的 tsdown 版本。对于 Vite+ pack 配置,它会读取已安装的 vite-plus/dist/toolchain.json。这也包括在迁移重写导入之前已经导入 vite-plus/pack 的 tsdown.config.* 文件。独立的 tsdown 配置使用其已安装的 tsdown 版本。没有此清单的旧版 Vite+ 会保留旧的默认值。对于 tsdown 0.23 或更高版本,迁移不会插入兼容性默认值;对于较旧或未知版本,如果缺少 deps.resolveDepSubpath,则会将其设置为 true 以保留之前的默认行为。启用 ATTW 检查且未设置 profile 时,会使用 profile: 'strict'。显式值(包括 false)保持不变。

插入的设置会附带文档链接和采用新默认值的说明。移除 deps.resolveDepSubpath: true 可按原样保留外部子路径导入。移除插入的 attw.profile: 'strict' 可使用新的 esm-only 解析配置,但这会跳过 node10 和 CommonJS 解析检查。如果软件包需要保留之前的行为,请保留相应设置。对于显式设置,迁移不会添加这些注释。

转换过程不会执行配置代码。包含展开属性、计算键或重复键的对象,以及冲突的新旧选项,都需要手动检查。动态布尔选择器保持不变。不相关的 Vite 和插件选项保持不变。迁移后运行 vp pack 检查结果。Node.js 要求、TypeScript 模块解析和程序化 build() 返回值需要单独检查。

任务缓存配置 ​

vp migrate 会将每个任务顶层的 env、untrackedEnv、input 和 output 移入任务的 cache 对象。它会更新 vite.config.* 中静态的 run.tasks 对象。现有 Vite+ 项目(包括工作区包)即使不使用 --full 也会执行此迁移。

之前的任务配置更新后的任务配置
没有 cache添加 cache 并移入这些设置
cache: true将 true 替换为移入的设置
cache: { ... }将移入的设置添加到现有对象
ts
// Before
build: {
  command: 'vp build',
  env: ['NODE_ENV'],
  input: [{ auto: true }, '!dist/**'],
},

// After
build: {
  command: 'vp build',
  cache: {
    env: ['NODE_ENV'],
    input: [{ auto: true }, '!dist/**'],
  },
},

移入的设置会保留原有文本和缩进,迁移后的格式化步骤会将它们缩进到 cache 中。位于设置前独立一行的注释,以及同一行设置后面的 // 注释,都会随设置一起移动。

此转换不会执行配置代码。如果任务存在以下情况,迁移会保持任务不变并给出手动迁移警告:

  • 存在展开属性、计算键、转义键或重复键;
  • 被移入的设置写成方法,例如 input() { ... };
  • 被移入设置与其逗号之间有注释,或 cache 中最后一个设置之后有注释;
  • cache 中已存在一个被移入的设置;
  • cache 的值不是 true 或对象字面量。

如果任务位于 vite.config.* 中的对象内,但该对象本身不是导出的配置(例如配置引用的变量,或传给 mergeConfig 的对象),迁移也会发出警告。其他模块中创建的任务(例如共享辅助函数创建的任务)不会被检测到。请手动移入这些设置;vp run 会列出仍需处理的任务。

当 cache: false 时,移入的设置不会生效,因此请决定移除它们还是启用缓存。对于其他方面均已更新的项目,vp migrate 会打印这些警告,但不会继续执行迁移的其余部分。

依赖规则 ​

版本选择 ​

迁移会保留项目的软件包管理器声明策略。现有的 packageManager 或 devEngines.packageManager 字段仍是事实来源,而仅依赖锁文件的项目仍保持未固定状态。具体来说,迁移不会将其自身安装时使用的软件包管理器版本转变为贡献者或 CI 所需的新精确版本。迁移推断出未声明的软件包管理器时,会打印警告;希望显式固定版本的项目可以使用 vp env pin。

下面概述各工具链依赖项的处理方式:

依赖处理方式
vite-plus在迁移的包中添加;普通范围会重新固定为明确的目标版本,可直接指定或通过 catalog 指定。
vite保留现有声明并指向 core 别名。仅使用 vite-plus 的 pnpm 包不需要新增 vite 条目(见 Vite 和 Overrides)。
vitest常见的 node 模式下会移除,因为 vite-plus 会间接提供它。仅在直接需要时保留或添加。
@vitest/*直接安装的锁步包会与捆绑的 Vitest 版本对齐(见 Vitest 生态包)。
@voidzero-dev/vite-plus-test在所有位置移除,包括 dependencies、overrides、resolutions 和 catalog 别名。导入会重写为当前的 vite-plus/test* 接口。
  • vite-plus 固定为执行迁移的 CLI 的具体版本,绝不会使用 latest dist-tag。
  • vite 别名指向同一 Vite+ 版本中的 @voidzero-dev/vite-plus-core。
  • 基于 catalog 的清单可能包含 catalog: 或命名 catalog 引用。迁移会保留该引用,并将所引用的 catalog 值更新为具体的工具链目标版本。
  • 有意指定的协议固定会被保留:workspace:、file:、link:、npm:、github:、Git URL 和 HTTP URL。
  • 迁移会协调每个工作区包,而不只是根清单。共享的 overrides 和 catalogs 保留在工作区根目录;提供 peer 的依赖属于每个需要它的包。

Vite 和 Overrides ​

在 pnpm 包中使用 vite-plus 无需声明 vite。vite-plus 包含一个别名指向其 core 的 vite 依赖,可满足 Vitest 所需的 peer。迁移会为其他 Vite 使用方保留工作区 override,但不会仅仅因为某个包使用了 vite-plus 就添加 vite。

  • 仅仅因为存在根 override,直接的 vite 声明也不会被移除。
  • 普通别名或过时别名会被规范化;命名 catalog 引用会被保留。
  • 在 pnpm 下,受管理的 override 键会使用显式的 @* 范围(vite@*、vitest@*)。pnpm 会通过替换每个清单(包括 importer 清单)中声明的 spec 来应用 override。裸键会匹配任意 spec,包括 catalog:,随后 vp up 会将该引用重写为具体版本。@* 范围会使 override 作用于传递依赖和 peer 声明使用的 semver 范围,同时将 catalog: 引用留给 catalog,因为 catalog 已经会将它们解析为 Vite+ core。对于仍使用裸键的项目,迁移会重新设置其键,并保留其命名 catalog 选择。
  • Bun 会将其 core 别名镜像为直接依赖,以供 peer resolver 使用;npm 的浏览器提供程序布局可能需要顶层 vite 依赖边,以便嵌套的 Vitest 包解析 vite。

何时直接需要 Vitest ​

当满足以下任一条件时,迁移会以精确的捆绑版本保留或添加包本地的 vitest:

  • 已安装的依赖具有非可选的 vitest peer,无论是精确版本还是范围;
  • 该包使用 Vitest 浏览器模式或选择加入的浏览器提供程序;
  • 源码或 TypeScript 配置保留了上游 vitest 引用;
  • 该包声明了 @nuxt/test-utils;或
  • 依赖元数据不可用,且已有的直接 vitest 可能正在满足未知的必需 peer。

检测会读取已安装的 peer 元数据,因此即使 vite-plugin-gherkin 这类集成的名称不包含 vitest,也能正确处理。

当某个包符合条件时,迁移会:

  • 将 vitest 添加到该包,而不是不加区分地添加到每个工作区包;
  • 在支持时使用现有的 catalog 引用,否则使用精确的捆绑版本;并且
  • 保留匹配的工作区 override 或 resolution,使依赖图解析为单一的 Vitest 版本。

仅有 peer 声明并不会安装 Vitest。如果保留下来的 peerDependencies.vitest 使用了迁移将移除的 catalog 条目,则会先将其解析为公开的 peer 范围。

Vitest 生态包 ​

官方当前的 @vitest/* 包通常与 Vitest 同步发布。迁移会对项目直接安装的包进行版本对齐,包括 @vitest/coverage-v8、@vitest/coverage-istanbul、@vitest/ui 和 @vitest/web-worker:

  • 当包管理器支持 catalogs 时,会通过工具链 catalog 引用它们:保留现有的 catalog: / catalog:<name> 引用,为缺少 catalog 的包添加 catalog 条目,并将每个条目更新为捆绑的 Vitest 版本;
  • 当不支持 catalogs 时(npm、独立的 bun 项目,或预 catalog 版本的 pnpm/Yarn),会直接写入具体的捆绑版本。

不会对齐的包:

  • @vitest/eslint-plugin 遵循自己的版本线;
  • @vitest/coverage-c8 停留在较早版本,没有 Vitest 4 版本;以及
  • 第三方 vitest-* 集成保留各自兼容的版本,但它们所需的 Vitest peer 仍可能触发直接提供。

对于浏览器模式,基础的 @vitest/browser runtime 和 @vitest/browser-preview 由 Vite+ 捆绑,并作为直接依赖移除。Playwright 和 WebdriverIO 提供程序仍需选择加入:保留或注入的提供程序会通过首选工具链 catalog 以捆绑的 Vitest 版本引用(不支持 catalogs 时则具体写入),并且其 playwright 或 webdriverio peer 会一并安装。

浏览器模式下,Vite+ 会捆绑基础 @vitest/browser runtime 和 @vitest/browser-preview;迁移会移除它们的直接依赖。Playwright 提供程序仍需选择加入。迁移会通过首选的工具链 catalog 或依赖条目,为 @vitest/browser-playwright 使用捆绑的 Vitest 版本,并同时安装其 playwright peer。

迁移会在重写导入之前检测 Playwright 的使用情况,包括旧版 vitest/browser-playwright、vitest/browser/providers/playwright 和 vitest/plugins/browser-playwright 别名。

vp migrate 会将已移除的 Vite+ WebdriverIO 别名恢复为 @vitest/browser-webdriverio;如果缺少该提供程序,则添加 ^5.0.0。它会升级较旧版本、收窄允许低于 5.0.0 的范围、更新引用的 catalog 条目,并移除强制使用较旧提供程序的 override。符合最低版本要求的版本和范围会保持不变。迁移会确保存在必需的 webdriverio peer,并添加与捆绑 Vitest 相匹配的 @vitest/browser override(Yarn 使用 resolutions)。之后的社区提供程序升级由你自行管理。请参阅社区 WebDriverIO 提供程序。

源码重写规则 ​

vite 导入 ​

vite 和 vite/* 导入仅在配置入口文件中重写为 vite-plus:vite.config.*、vitest.config.* 以及迁移解析出的任何配置文件。其他所有文件都会保留其 vite 导入,原因有二:

  • vite-plus 并不保证是 Vite 暴露接口的超集。它只拥有 defineConfig、defineProject 和 lazyPlugins,因此重写诸如 createBuilder 或 loadConfigFromFile 这样的透传符号(包括 typeof import('vite') 类型位置中的符号)可能导致破坏。
  • 未重写的 vite 导入仍会通过 Vite+ 项目中的 @voidzero-dev/vite-plus-core 别名解析。

插件包(以 vite-plugin- 或 unplugin- 开头的非 scoped 名称,或在 peerDependencies/dependencies 中包含 vite 的包)即使位于配置文件中,也会跳过重写。此规则仅适用于 vite specifier。

declare module 'vite' 扩展遵循相同规则,并在配置文件之外保留。通过 core 别名,它们会到达同一个 @voidzero-dev/vite-plus-core 模块;该模块的 UserConfig 类型会从 vite-plus 获取 defineConfig,因此迁移后仍可正常工作;vite-plus 自身不导出 UserConfig 符号,因此重写后的 declare module 'vite-plus' 扩展将无法合并到任何内容。针对 vite-plus 自身接口的扩展需要手动针对 vite-plus 编写。

vitest 和浏览器导入 ​

  • 普通的 vitest 和 vitest/* 导入会重写为 vite-plus/test*。
  • 旧版 Playwright 和 WebdriverIO 提供程序导入会在此次重写之前检测,以免丢失其可选提供程序依赖。
  • scoped 的 @vitest/browser* 导入会重写为对应的 vite-plus/test/browser* 导出,并在需要时提供选择加入的提供程序。
  • 已有的 vite-plus/test* 导入保持不变。

Oxlint JS Plugin 导入 ​

Vite+ 捆绑了 Oxlint,因此迁移会移除独立的 oxlint 依赖。你自己的 Oxlint JS 插件会按名称导入 authoring API。当该依赖消失后,此导入将无法解析。随后 vp lint 会加载插件失败。

迁移会将这些导入重新指向 Vite+:

  • 它会将 @oxlint/plugins 重写为 vite-plus/lint/plugins。
  • 它会将 oxlint/plugins-dev 重写为 vite-plus/lint/plugins-dev。
  • 当 oxlint 导入命名的是 authoring API 中的绑定(例如 defineRule、definePlugin 或 Context)时,会将其重写为 vite-plus/lint/plugins。旧版 Oxlint 从主入口暴露该 API。现在它位于 @oxlint/plugins 中。

通过 Vite+ 导入始终匹配 Vite+ 捆绑的 Oxlint 版本。你无需再固定第二个包。该导入也能从任何已经依赖 vite-plus 的包中解析。

迁移会保留以下三种形式:

  • 仅命名配置接口的 oxlint 导入,例如 defineConfig、OxlintConfig 或 OxlintOverride。这些导入仍会针对独立包解析。
  • 默认和 namespace oxlint 导入。它们没有命名绑定,因此迁移无法区分这两个接口。
  • 裸副作用 oxlint 导入,原因相同。

如果某个包在 dependencies 或 peerDependencies 中声明了 oxlint 或 @oxlint/plugins,或者在 optionalDependencies 中声明了 @oxlint/plugins,迁移也会跳过该包。这些依赖可以提供已发布的 Oxlint 插件,而使用它们的消费者可能不会运行 Vite+。

当源码、包导入别名或构建后的插件仍引用 @oxlint/plugins 时,清理过程会保留对 @oxlint/plugins 的开发依赖。这包括 dist、build 和 out 等目录中被忽略的输出。

永远不会重写的内容 ​

  • declare module 'vitest' 和 declare module '@vitest/browser*':模块扩展必须保留上游模块身份。
  • 保留下来的引用,例如 compilerOptions.types、require.resolve、import.meta.resolve 和 vitest/package.json,需要包本地的 Vitest(见何时直接需要 Vitest)。
  • 在声明了 @nuxt/test-utils 的包中,所有 vitest 和 vitest/* 模块 specifier 都会在整个包范围内保留:Nuxt 转换需要上游身份,否则可能注入重复的 vi 导入。此例外不适用于同级包,也不适用于 scoped 的 @vitest/browser* 导入。

prefer-vite-plus-imports lint 规则遵循相同的 Nuxt 例外,因此 lint 自动修复也会保留这些导入。

包脚本重写规则 ​

迁移会重写 package.json 脚本中由 Vite+ 工具链提供的命令,同时保留其参数:

之前之后
vitevp dev,或对应的 vp 子命令
vitestvp test
oxlintvp lint
oxfmtvp fmt
tsdownvp pack
lint-stagedvp staged
eslintvp lint,当其可选迁移运行时
prettiervp fmt,当其可选迁移运行时
tsupvp pack,当其可选迁移运行时

对于通过 bunx 启动的命令,迁移会保留 bunx 及其 --bun 标志(保留用户选择的 runtime),只重写受管理的命令。当 bunx 位于 run 或 -- 等命令启动器分隔符之后时,同样有效:

之前之后
bunx --bun vite buildbunx --bun vp build
bunx --bun vitest runbunx --bun vp test run
portless --tailscale run bunx --bun viteportless --tailscale run bunx --bun vp dev
dotenv -e .env.test -- bunx --bun oxlint --type-awaredotenv -e .env.test -- bunx --bun vp lint --type-aware

不相关的 bunx 命令以及其他包执行器形式保持不变。

持续集成规则 ​

迁移会将 .github 下 GitHub Actions 工作流和复合操作中的精确 voidzero-dev/setup-vp@v1 引用替换为该 Vite+ 版本已知的最新精确 setup-vp 版本。冻结的 v1 标签不会接收新版本。已有的精确版本和 commit SHA 保持不变。

Node.js 版本规则 ​

迁移会将旧版 Node.js 版本管理器文件转换为 .node-version,这是 Vite+ 读取的格式。对于已有的 Vite+ 项目,此转换属于完整设置范围,因此会通过 vp migrate --full 运行;全新迁移则会无条件运行。

  • .nvmrc 和 Volta 的 volta.node 固定会转换为 .node-version。已有的 .node-version 会被保留。
  • 移除 .nvmrc 时,.github/workflows/*.{yml,yaml} 以及 .github 下复合操作(.github/**/action.{yml,yaml})中的任何 actions/setup-node node-version-file: .nvmrc 引用都会改指向 .node-version,以免 CI 因“node version file ... does not exist”而失败。

包管理器规则 ​

pnpm ​

根设置位置。 pnpm 10.6.2+ 使用 pnpm-workspace.yaml 作为受支持根设置的唯一来源。迁移会将已识别的 package.json#pnpm 字段移动到该文件中,包括 overrides、peer 规则、patch 设置、包扩展、架构和构建策略、审计/更新配置以及配置依赖。迁移会在 pnpm 对象为空时移除它,并保留可能属于其他工具的未知键。

  • 当两个文件定义了相同的迁移设置时,会递归合并对象条目并保留不重复的数组条目。在冲突的标量叶节点上,package.json#pnpm 中的值优先,同时保留仅存在于工作区文件中的同级条目。
  • 在 pnpm 10.6.2 之前,这些设置保留在 package.json#pnpm 中。(工作区设置支持是逐步加入的:一般设置从 10.5.0 开始,overrides 从 10.5.1 开始,peerDependencyRules 从 10.6.2 开始。pnpm 11 不再读取旧版 package.json 设置。)

Catalogs。 Catalogs 是从 pnpm 9.5.0 开始支持的独立功能,与上述设置边界无关。即使在 10.6.2 以下的版本中,overrides 仍保留在 package.json#pnpm,迁移也会将工作区 catalog 从过时的 wrapper 别名中重写出来,并将 catalog: overrides 保留为引用,而不是内联为具体版本。

  • 依赖引用、默认和命名 catalogs、overrides 以及 peerDependencyRules 会彼此保持一致。
  • pnpm 接受顶层 catalog 或 catalogs.default 作为逻辑默认 catalog,但不能同时使用二者。迁移会保留现有形式,不会在其旁边创建另一种形式。
  • 当已有的命名 catalog 已拥有 vite-plus、vite 或 vitest 时,迁移会复用该受管理的工具链 catalog,用于新添加的依赖和 overrides。只有在没有可复用的受管理或默认 catalog 时,才会创建顶层默认 catalog。

其他规则。

  • 声明了 vite-plus 但没有直接 vite 声明的包会保留这种布局(见 Vite 和 Overrides)。
  • 不相关的选择器形式和对象值 overrides 会被保留。

npm ​

  • 在添加匹配的 override 之前,会先规范化直接别名,以免 npm 因 EOVERRIDE 失败。
  • 当真实的 Vite 安装切换为 core 别名时,会先移除过时的 Vite 安装和锁文件状态,然后再重新安装。
  • 当嵌套的 Vitest 包无法解析 vite 时,选择加入的浏览器提供程序布局会获得顶层的 vite 边。

Yarn ​

  • Vite+ 不支持 Plug'n'Play。迁移会检测显式和隐式 PnP,并将项目转换为 nodeLinker: node-modules,同时保留所有不相关的 .yarnrc.yml 设置。--no-interactive 会接受此转换;进程级别的 YARN_NODE_LINKER=pnp 必须由调用方修复。
  • Catalog 引用和用户的 hoisting 设置会被保留。
  • 为避免工作区 hoisting 隔离下出现多个 Vitest 副本,迁移会在可能时应用包级修复;当无法安全更改隔离时则发出警告。

Bun ​

  • Bun catalogs 仅能在工作区内解析(根 package.json 中包含非空的 workspaces)。在 bun 工作区中,现有的顶层或工作区 catalog 位置以及命名 catalog 引用都会被保留。独立的(单包)bun 项目会保留具体 spec,并且不会获得 catalog 字段,因为 bun install 无法在工作区之外解析 catalog:。
  • core 别名会被镜像为直接的 vite 依赖,使 Bun 在应用 overrides 之前能够看到 peer provider。

迁移后 ​

  • 每个 Vite 配置都会检查是否存在与 Rolldown 不兼容的模式(例如 manualChunks)。发现的任何内容都会作为警告报告;配置不会被修改。
  • 依赖会重新安装一次,以刷新锁文件。如果安装失败,迁移会报告错误并以非零状态退出。
  • 成功迁移后,vp fmt 会运行于迁移过程中发生变更的文件上,但会排除 Git 工作区中原本就处于脏状态的路径。Oxfmt 会选择受支持的格式;非 Git 项目会保留全项目格式化。项目仍使用 Prettier 时会跳过格式化。格式化失败会作为警告报告,因此迁移结果和手动格式化命令仍然可用。