Vite Plus 迁移记录与踩坑总结

本文记录了将 Tona 项目从传统 Vite 工具链完整迁移到 Vite+ 的全过程,包括迁移策略、配置调整、踩坑经历与最终收益。

1. 项目背景

Tona 是一个专为博客园设计的皮肤开发框架,采用 monorepo 架构,包含:

  • 12 个 packages:核心库、UI 组件、Hooks、工具函数、CLI 工具等
  • 3 个 themes:geek、reacg、shadcn 三套博客皮肤

关于 Tona 的详细介绍,请查看这篇博文《AI × 博客园皮肤》

更详细的介绍

2. 为什么决定迁移到 Vite+

Vite+ 刚刚发布,MIT 协议,免费且开源。我十分喜欢 Vite 的 API 的设计和兼容性,对于 Tona, Vite 几乎每个版本都有经历,从 Vite 0.8 版本开始使用, 逐步过渡到 Vite 8,每次升级都轻而易举,相信这次也不例外!

Vite+ 将现代 Web 开发所需的工具整合到一个统一的工具链中:

工具 用途
Vite + Rolldown 开发服务器和应用构建
Vitest 测试框架
Oxlint + Oxfmt 代码检查和格式化
tsdown 库构建和独立可执行文件
Vite Task 任务编排

统一的工作流

bash 复制代码
vp dev      # 开发服务器
vp check    # 格式化 + lint + 类型检查
vp test     # 运行测试
vp build    # 生产构建
vp run      # 任务执行

在 JavaScript 生态中,开发者需要管理越来越多的工具:运行时(Node.js)、包管理器(pnpm/npm/yarn)、开发服务器、linter、formatter、测试运行器、打包器、任务运行器......每个工具都有自己的配置文件、版本管理和升级周期。

一个典型的前端项目需要配置多个工具:

json 复制代码
{
  "devDependencies": {
    "vite": "^5.x",
    "vitest": "^1.x",
    "tsdown": "^0.x",
    "@biomejs/biome": "^1.x",
    "lefthook": "^1.x"
  }
}

每个工具都有独立的配置文件、版本管理、升级周期。当它们之间存在兼容性问题时,调试成本极高。

复制代码
tona/
├── vite.config.ts
├── vitest.config.ts
├── tsdown.config.ts
├── biome.jsonc
├── lefthook.yml
└── .nvmrc

配置分散在多个文件中,维护成本高,且容易出现配置不一致的问题。How many config files does one need, right?

每个工具都需要单独安装和更新,版本冲突时有发生。特别是 tsdown 和 Vite 之间的依赖关系,经常导致 lock 文件冲突。正如 Vite+ 官方文档所指出的,这些问题在多团队组织中会被放大:依赖管理、构建基础设施和代码质量成为分散的责任,每个团队各自处理,往往没有人将其作为优先事项来负责。结果是依赖版本不同步、构建变慢、代码质量下降。

Vite+ 的底层组件使用 Rust 编写,提供了企业级的性能:

操作 性能提升
生产构建 比 webpack 快 40 倍
代码检查 比 ESLint 快 50-100 倍
代码格式化 比 Prettier 快 30 倍

更重要的是,统一工具链带来了额外的性能优势。例如,vp check 可以在单次运行中同时完成格式化、lint 和类型检查,比分开运行类型感知的 lint 规则和类型检查快 2 倍

对于 Tona 这样一个 monorepo 项目,Vite+ 的价值尤为明显:

  • 减少依赖数量 :移除 5 个工具依赖,简化 package.json
  • 统一配置管理:12 个 package 的配置风格统一
  • 简化 CI/CD:无需在 CI 中安装多个工具
  • 降低维护成本:工具升级只需更新 Vite+ 版本
  • 性能提升:构建和检查速度显著提升

3. 迁移前的项目结构和技术栈

原有技术栈

类别 工具/方案
构建工具 Vite + tsdown
测试框架 Vitest
代码规范 Biome(lint + format)
Git Hooks Lefthook
包管理 pnpm + pnpm-workspace
Node 版本管理 nvm
复制代码
tona/
├── packages/
│   ├── core/
│   │   ├── vite.config.ts
│   │   └── tsdown.config.ts      # 库构建配置
│   ├── hooks/
│   ├── ui/
│   ├── utils/
│   └── ...
├── themes/
│   ├── geek/
│   │   └── vite.config.ts
│   ├── shadcn/
│   │   └── vite.config.ts
│   └── reacg/
│       └── vite.config.ts
├── vite.config.ts                # 根配置
├── vitest.config.ts              # 测试配置
├── biome.jsonc                   # 代码规范
├── lefthook.yml                  # Git hooks
├── .nvmrc                        # Node 版本
└── pnpm-workspace.yaml

受影响的部分

部分 影响程度 说明
vite.config.ts 需要重写导入和配置结构
tsdown.config.ts 合并到 vite.config.ts
vitest.config.ts 合并到 vite.config.ts
biome.jsonc 迁移到 vite.config.ts
lefthook.yml 使用 Vite+ 内置 hooks
.nvmrc 使用 Vite+ 管理 Node 版本
package.json scripts 命令重写
源码导入语句 vite → vite-plus

4. 迁移整体策略

Vite+ 提供了 coding agent 专用的迁移提示词,我先让 agent 走一遍再手动处理剩余问题。

md 复制代码
Migrate this project to Vite+. Vite+ replaces the current split tooling around runtime management, package management, dev/build/test commands, linting, formatting, and packaging. Run `vp help` to understand Vite+ capabilities and `vp help migrate` before making changes. Use `vp migrate --no-interactive` in the workspace root. Make sure the project is using Vite 8+ and Vitest 4.1+ before migrating.

After the migration:

- Confirm `vite` imports were rewritten to `vite-plus` where needed
- Confirm `vitest` imports were rewritten to `vite-plus/test` where needed
- Remove old `vite` and `vitest` dependencies only after those rewrites are confirmed
- Move remaining tool-specific config into the appropriate blocks in `vite.config.ts`

Command mapping to keep in mind:

- `vp run <script>` is the equivalent of `pnpm run <script>`
- `vp test` runs the built-in test command, while `vp run test` runs the `test` script from `package.json`
- `vp install`, `vp add`, and `vp remove` delegate through the package manager declared by `packageManager`
- `vp dev`, `vp build`, `vp preview`, `vp lint`, `vp fmt`, `vp check`, and `vp pack` replace the corresponding standalone tools
- Prefer `vp check` for validation loops

Finally, verify the migration by running: `vp install`, `vp check`, `vp test`, and `vp build`

Summarize the migration at the end and report any manual follow-up still required.

4.1 迁移原则

  1. 一次性迁移:项目处于活跃开发期,适合一次性迁移
  2. 自动化优先:使用 Vite+ 提供的迁移工具自动处理
  3. 逐步验证:每完成一个阶段立即验证
  4. 保留回滚能力:迁移前创建 git 分支

4.2 迁移流程

复制代码
┌─────────────────┐
│ 1. 版本检查       │ 确保 Vite 8+, Vitest 4.1+
└────────┬────────┘
         │
┌────────▼────────┐
│ 2. 自动迁移       │ 运行 vp migrate --no-interactive
└────────┬────────┘
         │
┌────────▼────────┐
│ 3. 配置合并       │ tsdown.config.ts → vite.config.ts
└────────┬────────┘
         │
┌────────▼────────┐
│ 4. 清理旧依赖     │ 移除 biome, lefthook, vitest 等
└────────┬────────┘
         │
┌────────▼────────┐
│ 5. 配置迁移       │ 格式化、lint、git hooks
└────────┬────────┘
         │
┌────────▼────────┐
│ 6. 验证测试       │ install → check → test → build
└─────────────────┘

5. 实际迁移步骤

5.1 版本检查与安装

首先检查现有版本是否满足迁移要求:

bash 复制代码
# Vite+ 要求 Vite 8+, Vitest 4.1+
vp --version

确认满足要求后,执行自动迁移:

bash 复制代码
vp migrate --no-interactive

迁移工具自动完成了以下工作:

  • 重写 vite 导入为 vite-plus
  • 重写 vitest 导入为 vite-plus/test
  • 更新 package.json 中的依赖和 scripts
  • 更新 pnpm-workspace.yaml

5.2 配置合并

原 tsdown.config.ts

typescript 复制代码
import { defineConfig } from 'tsdown'

export default defineConfig({
  platform: 'browser',
  entry: ['./src/index.ts'],
  format: ['esm'],
  dts: true,
  clean: true,
  external: ['preact'],
})

合并后的 vite.config.ts

typescript 复制代码
import { defineConfig } from 'vite-plus'

export default defineConfig({
  pack: {
    platform: 'browser',
    entry: ['./src/index.ts'],
    format: ['esm'],
    dts: true,
    clean: true,
    deps: {
      neverBundle: ['preact'],
    },
  },
})

关键变更说明

原配置 新配置 原因
external deps.neverBundle tsdown 配置已弃用 external
import from 'tsdown' import from 'vite-plus' 统一从 vite-plus 导入
独立文件 pack block 配置集中管理

5.3 格式化配置迁移

将 Biome 配置迁移到 vite.config.ts:

原 biome.jsonc

json 复制代码
{
  "formatter": {
    "enabled": true,
    "formatWithErrors": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "asNeeded",
      "trailingCommas": "all"
    }
  }
}

迁移后的 vite.config.ts

typescript 复制代码
import { defineConfig } from 'vite-plus'

export default defineConfig({
  fmt: {
    singleQuote: true,
    semi: false,
    tabWidth: 2,
    printWidth: 80,
    trailingComma: 'all',
  },
  lint: {
    rules: {
      'no-explicit-any': 'off',
      'no-non-null-assertion': 'off',
    },
  },
})

踩坑:Oxfmt 使用 Prettier 兼容的配置名称,而非 Biome 的命名方式。Oxfmt 提供了完整的 Prettier 兼容性,配置迁移相对平滑。

5.4 Git Hooks 配置

移除 Lefthook,使用 Vite+ 内置的 Git hooks:

bash 复制代码
# 移除旧配置
rm lefthook.yml

# 配置 Vite+ hooks
vp config --hooks

Vite+ 会自动配置 pre-commit 钩子,执行 vp staged 命令对暂存文件进行检查。

5.5 Node 版本管理

移除 .nvmrc,使用 Vite+ 管理:

bash 复制代码
# 移除旧配置
rm .nvmrc

# 固定 Node 版本
vp env pin 22.18.0

这会创建 .node-version 文件,Vite+ 会自动安装和管理对应版本的 Node.js。Vite+ 会自动检测并使用正确的包管理器(pnpm、npm 或 Yarn)。

5.6 Scripts 更新

更新前

json 复制代码
{
  "scripts": {
    "test": "vitest test",
    "lint": "biome check --write",
    "prepare": "lefthook install"
  }
}

更新后

json 复制代码
{
  "scripts": {
    "test": "vp test",
    "lint": "vp lint",
    "fmt": "vp fmt",
    "check": "vp check"
  }
}

注意:prepare script 已移除,Vite+ 的 hooks 配置由 vp config --hooks 管理。

6. 迁移过程中遇到的坑

6.1 类型导入问题

问题 :某些文件使用 import typevite-plus 导入类型时,dts 生成失败。

原因vite-plus 的类型定义中引用了 vite-plus-test,导致类型解析链断裂。

解决 :将类型导入改回 vite

typescript 复制代码
// 问题代码
import type { Plugin, UserConfig } from 'vite-plus'

// 解决方案
import type { Plugin, UserConfig } from 'vite'

6.2 CSS 文件名不匹配

问题tona-sonner 包构建后输出 style.css,但 package.json exports 定义的是 index.css

原因 :tsdown 默认将 CSS 文件命名为 style.css,与原配置不一致。

解决

  1. 更新 package.json exports:
json 复制代码
{
  "exports": {
    ".": {
      "types": "./dist/index.d.mts",
      "import": "./dist/index.mjs"
    },
    "./dist/style.css": "./dist/style.css"
  }
}
  1. 更新导入路径:
typescript 复制代码
// 更新前
import 'tona-sonner/dist/index.css'

// 更新后
import 'tona-sonner/dist/style.css'

6.3 external 配置弃用

问题 :构建时出现警告 external is deprecated. Use deps.neverBundle instead.

原因:tsdown 更新了配置 API。

解决

typescript 复制代码
// 更新前
{
  external: ['preact']
}

// 更新后
{
  deps: {
    neverBundle: ['preact']
  }
}

6.4 单文件输出警告

问题 :使用 outputOptions.file 配置单文件输出时出现警告:

复制代码
[INVALID_OPTION] Warning: Invalid value for option "output.dir"

原因:rolldown 内部配置校验问题,但构建结果正确。

解决:暂时接受此警告,等待上游修复。配置保持不变:

typescript 复制代码
{
  outputOptions: {
    file: 'dist/loader.min.js',
  },
}

6.5 插件兼容性警告

问题@preact/preset-vite 插件触发警告:

复制代码
warning: `esbuild` option was specified by "vite:preact-jsx" plugin.
This option is deprecated, please use `oxc` instead.

原因:上游插件尚未适配 Vite+ 的 oxc 编译器。

解决 :暂时无法解决,需等待插件更新。可通过 logLevel: 'warn' 减少日志输出。

6.6 开发脚本适配

问题scripts/dev-theme.ts 无法正确检测 Vite+ 服务器启动完成。

原因:Vite+ 的启动日志格式与 Vite 不同。

解决 :添加对 Local: 输出的检测:

typescript 复制代码
// 更新前
if (data.includes('ready in')) {
  isReady = true
}

// 更新后
if (data.includes('ready in') || data.includes('Local:')) {
  isReady = true
}

7. 迁移后的效果

7.1 依赖精简

迁移前

json 复制代码
{
  "devDependencies": {
    "@biomejs/biome": "^1.x",
    "lefthook": "^1.x",
    "vite": "^5.x",
    "vitest": "^1.x",
    "tsdown": "^0.x"
  }
}

迁移后

json 复制代码
{
  "devDependencies": {
    "vite-plus": "latest"
  }
}

减少 4 个直接依赖,简化了依赖树。

7.2 配置文件精简

迁移前 迁移后
vite.config.ts (12个) vite.config.ts (12个)
tsdown.config.ts (11个) 合并到 vite.config.ts
vitest.config.ts (3个) 合并到 vite.config.ts
biome.jsonc (1个) 合并到 vite.config.ts
lefthook.yml (1个) 移除
.nvmrc (1个) .node-version (1个)

总计:从 29 个配置文件减少到 13 个。

7.3 构建验证

命令 结果
vp install ✅ 成功
vp check ✅ 0 errors, 22 warnings
vp test ✅ 205/206 测试通过
vp pack (packages) ✅ 12 个包构建成功
vp build (themes) ✅ 3 个主题构建成功

7.4 开发体验提升

  • 命令统一 :所有命令以 vp 开头,无需记忆多个工具命令
  • 配置集中:修改配置只需编辑一个文件
  • 版本管理简化 :工具升级只需更新 vite-plus 版本
  • 性能提升:得益于 Rust 底层实现,构建和检查速度显著提升

7.5 vp check 的优势

vp check 命令可以在单次运行中完成格式化、lint 和类型检查,速度更快:

bash 复制代码
$ vp check
pass: All 42 files are correctly formatted (88ms, 16 threads)
pass: Found no warnings, lint errors, or type errors in 42 files (184ms, 16 threads)

7.5 构建耗时大大减少

得益于 Vite+ 缓存机制,多包构建耗时明显减少:

8. 总结

8.1 哪些项目适合迁移

  1. 新项目:没有历史包袱,可以直接使用 Vite+ 初始化
  2. 工具链复杂的项目:依赖多个工具,配置分散,维护成本高
  3. monorepo 项目:多个包需要统一工具链
  4. 追求简化配置的项目:希望减少配置文件数量
  5. 对性能有要求的项目:需要更快的构建和检查速度

8.2 哪些项目不建议迁移

  1. 依赖特定 Vite 插件的项目:某些插件可能尚未适配
  2. 构建流程高度定制的项目:可能需要等待 Vite+ 支持更多配置选项
  3. 生产环境稳定性要求极高的项目:建议等待 Vite+ 更加成熟

8.3 迁移成本与收益

成本

  • 时间成本:中小型项目约 2-4 小时,大型 monorepo 约 1-2 天
  • 学习成本:需要熟悉 Vite+ 的配置结构和 CLI 命令
  • 兼容性处理:可能需要处理插件兼容性问题

收益

  • 维护成本降低:统一工具链,减少版本冲突
  • 配置简化:配置文件数量减少 50%+
  • CI/CD 简化:无需安装多个工具
  • 开发体验提升:统一的命令行接口
  • 性能提升:构建速度提升 40 倍,lint 速度提升 50-100 倍

8.4 迁移建议

  1. 先备份:迁移前创建 git 分支,确保可回滚
  2. 自动化优先 :使用 vp migrate 自动处理大部分工作
  3. 逐步验证:每完成一个阶段立即运行测试和构建
  4. 保留文档:记录迁移过程中的问题和解决方案

9. 附录

9.1 完整的 vite.config.ts 示例

typescript 复制代码
import { defineConfig } from 'vite-plus'

export default defineConfig({
  // 测试配置
  test: {
    environment: 'happy-dom',
  },
  
  // 格式化配置(Prettier 兼容)
  fmt: {
    singleQuote: true,
    semi: false,
    tabWidth: 2,
    printWidth: 80,
    trailingComma: 'all',
    arrowParens: 'always',
    bracketSpacing: true,
  },
  
  // Lint 配置(ESLint 兼容,600+ 规则)
  lint: {
    rules: {
      'no-explicit-any': 'off',
      'no-non-null-assertion': 'off',
      'no-static-element-interactions': 'off',
    },
  },
  
  // 库构建配置
  pack: {
    platform: 'browser',
    entry: ['./src/index.ts'],
    format: ['esm'],
    dts: true,
    clean: true,
    deps: {
      neverBundle: ['preact'],
    },
  },
})

9.2 关键文件变更清单

文件 操作 说明
vite.config.ts 修改 导入改为 vite-plus
tsdown.config.ts 删除 合并到 vite.config.ts
vitest.config.ts 删除 合并到 vite.config.ts
biome.jsonc 删除 配置迁移到 vite.config.ts
lefthook.yml 删除 使用 Vite+ hooks
.nvmrc 删除 使用 Vite+ 管理
.node-version 新增 Vite+ Node 版本文件
package.json 修改 更新依赖和 scripts
pnpm-workspace.yaml 修改 更新 catalog

9.3 相关链接

相关推荐
guangzan1 个月前
AI × 博客园皮肤
ai·tona
guangzan1 个月前
为博客园注入现代 UI 体验:shadcn 皮肤上线
typescript·tailwindcss·shadcn ui·tona