目标:一份能直接照着敲的升级手册,覆盖单文件组件、路由、Vuex、UI 库、测试、构建、CI、灰度、回滚所有环节。
阅读方式:左侧目录导航,右侧命令行直接复制。
预计耗时:阅读 30 min,实操 2~4 周。
0. 前置知识 & 一句话总结
| 你原来用的 | 升级后 | 一句话总结 |
|---|---|---|
| Vue 2.6 | Vue 3.5 | 先 2.7 再 3.5,减少 breaking |
| Webpack 4 | Vite 5 | 开发构建 30 s → 1 s |
| Vuex 3 | Pinia 2 | 官方推荐,TS 友好 |
| vue-router 3 | vue-router 4 | 模式声明方式全变 |
| element-ui | element-plus | 组件名不变,样式前缀变 |
| Jest 27 | Vitest 2 | API 95 % 兼容,速度 ×3 |
| Options API | <script setup> | 新代码写新语法,旧代码可不动 |
1. 升级路线图(时间轴)
| 周次 | 任务 | 产出物 | 可灰度? |
|---|---|---|---|
| W0(半天) | 备份 + 评估 | 1. 打 tag vue2-final2. 生成 migration-report.html | ✅ |
| W1 | 工具链迁移 | 1. vite.config.ts 跑通2. npm run dev 秒开 | ✅ |
| W2 | 语法自动化 | 1. gogocode 转换 70 % 2. 剩余 30 % 人工 | ✅ |
| W3 | 依赖大版本 | 1. Router / Vuex → Pinia 2. UI 库对齐 | ❌(需一次性) |
| W4 | 测试 + 性能 | 1. 单元测试全绿 2. Lighthouse ≥ 90 | ✅ |
| W5 | 灰度上线 | 1. 蓝绿发布 2. Sentry 零报警 | ✅ |
2. W0:备份与评估(4 h)
2.1 一键备份
# 1. 代码
git tag vue2-final -m "last vue2 commit"
git push origin vue2-final
# 2. 镜像(示例)
docker build -t registry.xxx.com/myapp:vue2-final .
docker push registry.xxx.com/myapp:vue2-final
2.2 生成兼容性报告
npm i -D @vue/compat@3.5
# 临时把 main.js 改成 Vue3 入口
npx vue-compat-report > migration-report.html
浏览器打开报告,重点关注:
- 红色 ❌ 数量 → 必须改
- 黄色 ⚠️ 数量 → 运行期警告,可灰度
2.3 建立“降级页面”清单
| 页面 | 依赖库 | 是否支持 Vue3 | 策略 |
|---|---|---|---|
| /old/legacy | vue2-editor | 无 | iframe 嵌入,暂缓 |
| /dashboard | echarts 4 | 有 5.x | 升级 |
3. W1:构建工具 Webpack → Vite(2 d)
3.1 安装
npm i -D vite @vitejs/plugin-vue2 sass
# 如用 jsx
npm i -D @vitejs/plugin-vue2-jsx
3.2 创建 vite.config.ts(可直接抄)
import { defineConfig } from 'vite'
import vue2 from '@vitejs/plugin-vue2'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue2()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'~': resolve(__dirname, 'node_modules')
},
extensions: ['.js', '.jsx', '.vue', '.json', '.ts', '.tsx']
},
server: {
port: 9527,
open: true
},
build: {
target: 'chrome88',
outDir: 'dist',
sourcemap: true
}
})
3.3 移动 index.html
Webpack 的 html 在 public/index.html,Vite 要求放项目根目录并显式引入入口:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
3.4 处理环境变量
| Webpack 写法 | Vite 等价 |
|---|---|
process.env.VUE_APP_XXX | import.meta.env.VITE_XXX |
| 如存量太多,可装垫片: |
npm i -D vite-plugin-env-compatible
在 vite.config.ts 加:
import envCompatible from 'vite-plugin-env-compatible'
plugins: [vue2(), envCompatible()]
3.5 脚本替换
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
跑一下:
npm run dev
预期:冷启动 < 1 s,浏览器自动打开
localhost:9527。
4. W2:语法迁移(Options API → Composition API)
4.1 自动转换(gogocode)
npm i -D gogocode-cli gogocode-plugin-vue
npx gogocode -s ./src -t gogocode-plugin-vue -o ./src-converted
转换率参考:
- 全局 API(
Vue.use/Vue.filter)→ 90 % - 生命周期 → 85 %
- 过滤器 → 95 %
- 事件总线 → 0 %(需手工)
4.2 手工必改清单
| Vue2 | Vue3 | 备注 |
|---|---|---|
this.$on/$off/$once | 移除 | 用 mitt 或 tiny-emitter |
v-model 语法 | 默认 prop 改为 modelValue | 组件需加 emits: ['update:modelValue'] |
v-if + v-for | 优先级变化 | 同节点共存会报警告 |
key 使用 index | 可能导致错位 | 一律改为唯一 id |
slot="xxx" | v-slot:xxx | 兼容,但推荐新写法 |
destroyed 生命周期 | 改名 unmounted | gogocode 已转 |
4.3 事件总线替换(实例)
npm i mitt
新建 utils/bus.ts:
import mitt from 'mitt'
export default mitt()
组件内:
import bus from '@/utils/bus'
bus.emit('login', user)
bus.on('login', (user) => {})
5. W3:依赖大版本升级
5.1 Vuex 3 → Pinia 2(官方推荐)
npm i pinia
npm un vuex
创建 stores/index.ts:
import { createPinia } from 'pinia'
export default createPinia()
main.js 注册:
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './stores'
createApp(App).use(pinia).mount('#app')
把原 store/modules/user.js 改成:
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: '' }),
actions: {
setName(n) { this.name = n }
}
})
组件使用:
import { useUserStore } from '@/stores/user'
const user = useUserStore()
user.setName('tom')
5.2 vue-router 3 → 4
npm i vue-router@4
router/index.ts:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [/* 原数组 */]
})
export default router
Breaking:
- 原
mode: 'history'改为createWebHistory() base参数移入createWebHistory('/base/')
5.3 UI 库
| 旧 | 新 | 安装 |
|---|---|---|
| element-ui | element-plus | npm i element-plus |
| ant-design-vue | 4.x | npm i ant-design-vue@4 |
| vant | 2 → 4 | npm i vant@4 |
全局引入(element-plus 示例):
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(ElementPlus)
样式前缀变化:
el- 保留,但 CSS 变量名 --el-* 与 --ep-* 共存,主题覆盖需检查。
6. W4:测试与性能
6.1 单元测试 Jest → Vitest
npm i -D vitest @vue/test-utils@2 jsdom
vitest.config.ts:
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue2'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom'
}
})
把原 *.spec.js 里 import { mount } from '@vue/test-utils' 保留即可,95 % 用例直接绿。
6.2 端到端
Cypress 13 已支持 Vue3 组件测试:
npm i -D cypress @cypress/vue
cypress/component/Button.cy.ts:
import Button from './Button.vue'
import { mount } from '@cypress/vue'
it('renders', () => {
mount(Button, { props: { label: 'ok' } })
cy.contains('ok')
})
6.3 性能基线
npm i -D rollup-plugin-visualizer lighthouse-ci
vite.config.ts:
import { visualizer } from 'rollup-plugin-visualizer'
plugins: [vue(), visualizer({ filename: 'dist/stats.html' })]
CI 中增加:
- name: Lighthouse
run: |
npm run build
npx lhci autorun --config=lighthouserc.json
阈值示例(lighthouserc.json):
{
"ci": {
"assert": {
"assertions": {
"categories:performance": ["error", {"minScore": 0.9}]
}
}
}
}
7. W5:灰度与回滚
7.1 双镜像 Dockerfile
# 阶段 1:编译
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# 阶段 2:运行
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
构建两条流水线:
docker build -t registry.xxx.com/myapp:vue2-${CI_COMMIT_SHA} .
docker build -t registry.xxx.com/myapp:vue3-${CI_COMMIT_SHA} .
7.2 K8s 蓝绿发布
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: vue3 # 切换时改这里
ports:
- port: 80
回滚只需 kubectl patch service myapp -p '{"spec":{"selector":{"version":"vue2"}}}'
7.3 监控
- Sentry 8 支持 Vue3 源码 Map,上传命令:
sentry-cli releases files $VERSION upload-sourcemaps dist --url-prefix '~/'
- Grafana 面板增加
vue_compat_warning_total指标,>0 立即报警。
8. 超细节踩坑 50 条(2025 年整理)
| 序号 | 症状 | 原因 | 修复 |
|---|---|---|---|
| 1 | 控制台 defineEmits is not defined | 未安装 unplugin-vue-define-options | npm i -D unplugin-vue-define-options |
| 2 | v-model 报错 modelValue is required | 组件没写 emits: ['update:modelValue'] | 加上 |
| 3 | 样式 ~@/assets 找不到 | Vite 不支持 ~ | 去掉 ~ |
| 4 | Sass /deep/ 警告 | 已废弃 | 替换为 ::v-deep |
| 5 | require.context('./svg', false, /\.svg$/) 失败 | Vite 无 webpack API | 用 import.meta.glob('/src/svg/*.svg', { eager: true }) |
| 6 | globalProperties 在 TS 下无提示 | 未扩充 ComponentCustomProperties | 见下文 |
| 7 | JSX 组件 h 未定义 | 未自动注入 | import { h } from 'vue' |
| 8 | Pinia 页面刷新丢状态 | 未持久化 | pinia-plugin-persistedstate |
| 9 | Cypress 找不到组件 | 没装 @cypress/vue | 装 |
| 10 | Lighthouse 性能掉分 | 忘了开 gzip | vite-plugin-compression |
篇幅所限,完整 50 条放 GitHub 仓库,文末扫码自取。
9. TypeScript 支持(可选但强烈建议)
9.1 添加 TS
npm i -D typescript vue-tsc
npx vue-tsc --init
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"jsx": "preserve",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["vite/client", "vitest/globals"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
9.2 让 globalProperties 有类型
shims-vue.d.ts:
import { ComponentCustomProperties } from 'vue'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: typeof axios
}
}
10. 一键模板 & 工具地址
| 名称 | 地址 | 说明 |
|---|---|---|
| vue3-vite-ts-template | github.com/xiaogao007/… | 本文完整模板 |
| gogocode-plugin-vue | github.com/xiaogao007/… | 自动转换 |
| vue-compat-report | github.com/xiaogao007/… | 兼容性报告 |
| lighthouse-ci | github.com/xiaogao007/… | 性能门禁 |
11. 结语 & 彩蛋
升级完成后,记得:
- 把
Dockerfile里node:14换成node:20,构建时间再降 20 %。 - 用
npm pkg delete devDependencies清理废弃包,体积 ↓ 20 MB。 - 把
npm run dev的截图甩到群里,享受 1 s 冷启动的快感!
如果本文帮到了你,去 GitHub 点个小星星 ⭐,升级过程中有任何疑问,欢迎提 Issue,24 h 内回复。
Happy Vue3!