本文记录了将 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 迁移原则
- 一次性迁移:项目处于活跃开发期,适合一次性迁移
- 自动化优先:使用 Vite+ 提供的迁移工具自动处理
- 逐步验证:每完成一个阶段立即验证
- 保留回滚能力:迁移前创建 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 type 从 vite-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,与原配置不一致。
解决:
- 更新 package.json exports:
json
{
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
},
"./dist/style.css": "./dist/style.css"
}
}
- 更新导入路径:
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 哪些项目适合迁移
- 新项目:没有历史包袱,可以直接使用 Vite+ 初始化
- 工具链复杂的项目:依赖多个工具,配置分散,维护成本高
- monorepo 项目:多个包需要统一工具链
- 追求简化配置的项目:希望减少配置文件数量
- 对性能有要求的项目:需要更快的构建和检查速度
8.2 哪些项目不建议迁移
- 依赖特定 Vite 插件的项目:某些插件可能尚未适配
- 构建流程高度定制的项目:可能需要等待 Vite+ 支持更多配置选项
- 生产环境稳定性要求极高的项目:建议等待 Vite+ 更加成熟
8.3 迁移成本与收益
成本:
- 时间成本:中小型项目约 2-4 小时,大型 monorepo 约 1-2 天
- 学习成本:需要熟悉 Vite+ 的配置结构和 CLI 命令
- 兼容性处理:可能需要处理插件兼容性问题
收益:
- 维护成本降低:统一工具链,减少版本冲突
- 配置简化:配置文件数量减少 50%+
- CI/CD 简化:无需安装多个工具
- 开发体验提升:统一的命令行接口
- 性能提升:构建速度提升 40 倍,lint 速度提升 50-100 倍
8.4 迁移建议
- 先备份:迁移前创建 git 分支,确保可回滚
- 自动化优先 :使用
vp migrate自动处理大部分工作 - 逐步验证:每完成一个阶段立即运行测试和构建
- 保留文档:记录迁移过程中的问题和解决方案
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 相关链接
- Tona 项目地址:https://github.com/guangzan/tona
- 迁移 commit 详情:https://github.com/guangzan/tona/commit/1caa437229cc4aedba54f4e0b152500fc385929c
- Vite+ 官方文档:https://viteplus.dev