最近接到一个老项目的重构诉求——把一个体量不小的 Vue2 项目迁移到 Vue3,顺带把 Element-UI 升级到 Element-Plus。
听起来是个"理论上不难,但谁都不想干"的活。
我参与这个事情的前提是,我想通过 AI 来试试这件事的可能性。所以这篇文章的思路基础是"将 AI 能力最大化",而不是写一个代码转换工具。如果你是来找银弹的,这篇文章可能会让你失望;但如果你也在思考怎么让大型遗留项目的迁移不那么痛苦,希望下面的分析能给你一些参考。
为什么 Vue2 到 Vue3 这么难推动
市面上主流的批量迁移方案就是 gogocode,除此之外我暂时没找到其他特别合适的工具。
在之前的工作经验中,碰到类似场景,项目要么不维护了,要么走到了"用 Vue3 重做一遍"这条路。造成这个局面,我想有几个原因:
- 难以说清楚重构的价值。理论上不新增复杂交互的话,Vue2 完全够用。真要加新东西的时候,负责人要么咬咬牙造个轮子撑过去,要么没人愿意因为一个局部的点动全部。
- 重构的人大多不是原维护者,往往倾向按自己的风格用 Vue3 重新做一次。
- 决策者对重构结果期望过高——认为工作量不大、重构完直接能用、甚至还能顺手加改造。
- 过不去测试这一关。测试说没人力回归,重构者又很难负全责。
那渐进式重构呢?"跟着业务迭代一点点来"听起来很美,但实操中也有坑:
- 过渡期间多维护一套代码,成本增加
- 架构不好的项目,牵一发动全身
- 有些包只支持 Vue2,找不到 Vue3 替代品,可能还得自己维护一份
- 项目越大,这个马拉松跑得越让人疲惫
先说清楚:我下面聊的方案也解决不了以上这些问题。我提供的是工具和思路,不是仙女棒。大家也不要指望未来有什么东西能一键解决所有问题。我只是想提供一些选择,让大家了解自己的处境,尽可能走到让自己舒服的路线上。
传统方案长什么样
为了做对比,先假设一个大家可能第一反应想到的方案(不是推荐方案):
- 建一个 Vue3 脚手架
- 梳理 Vue2 依赖包的 Vue3 替代品,记录改造方式
- 选 1-2 个页面试重构
- 和测试沟通用例
- 分析页面依赖,确保内容完整
- 用 Vue3 重写页面(核心成本点)
- 自测
- 回归测试
- 总结,规划后续
- 循环 3-9 直到结束
其中第 6 步是最大的成本——所有和 Vue 相关的代码基本要重写,相当于重新做页面。还有个隐藏成本:万一之前的组件库不支持,还得重写组件库。
补充一下:用 gogocode 会快非常多,只不过 gogocode 的目标是用选项式在 Vue3 跑起来。如果能接受这个结果,也是一条可行的路。
我的思路:让 AI 做粗加工
经过大量尝试,我得出的建议是:AI 做语法转化的粗加工,人工做项目工程化和局部问题处理。
形成这个方案的主要工作量,就是"如何让粗加工做得更好"和"人如何高效地处理局部问题"。
用 AI 的优势是——如果处理得足够好,多少个项目都不是问题,而且可以通过不断优化 prompt 来逐渐提升质量。
劣势也很明显——让 AI 达到"足够好"的时间成本不容易估计,不够好的时候人工介入的成本也不好估计。
两条路线:选项式 vs 组合式
在具体方案之前,有几点说在前面:
- 方案不是完美的。AI 处理会通过一些冗余和"奇怪的写法"来减少人工介入,代码里会有脏东西。如果你是代码洁癖患者,这篇文章可以到此为止了。
- 重构能达到的最好效果就是保持原有项目的现状,不会顺带优化已有的性能和逻辑问题。
- 两条路线可以混合使用——比如先走路线一低成本跑起来,再对需要长期迭代的部分用路线二。
路线一:选项式——低成本切到 Vue3
目标:重构后能跑起来就好,后续新东西直接在 Vue3 基座上开发。
核心靠 gogocode——通过 AST 将 Vue2 选项式转为 Vue3 选项式,以及 Element-UI 转 Element-Plus。批量且快速。
但 gogocode 会留下一些问题(已发现的):
- 需要注入
window.$vueApp全局变量,多实例场景可能冲突 - 不处理
return (<></>)这种 JSX 写法 filters处理不完整(似乎是换行导致没命中规则)- 部分
v-model处理有误 - Element-Plus 的样式、国际化路径不会修正
- Icon 转换会吞掉属性
这些问题其实都不大,人工集中处理也用不了多少时间。如果 b、c、d 类问题较多,可以再用 AI 做一轮清扫。一个选项式风格的 AI 修复 prompt 示例:
# 任务
对上面的代码进行如下处理
## Vue 版本重构
- 确保代码符合 ES6 模块系统
- 重构为非 TypeScript 的 Vue3 选项式风格
- Element-Plus 中 message 改为 import { ElMessage } from 'element-plus'
## 约束
- 多级引用使用可选链(?.)
- 即使不需要修改也返回原代码
- 只返回代码文本,不返回 markdown 格式
路线二:组合式——多投入一些,换来更好的可维护性
是的,这个方案可以将 Vue2 直接转到组合式。看不上选项式的,可以试这条路。
不过老实说,鉴于当前还没有实际完全跑通的项目,我个人觉得先让项目在 Vue3 环境跑起来,再半自动化转组合式,信心会更强。
重度依赖大模型
组合式的重构会重度依赖大模型。我使用的是通义千问-Max-Latest,原因很现实:
- 当时在公司内免费,代码转换的 token 量巨大,成本很关键
- 支持流式输出,代码多的时候也能输出完
- 企业内合作没有安全顾虑
我写了一个批量处理文件的工具,配置 prompt 和要处理的文件,以问答形式一个个文件完成转换。
时间成本的真实数据
从测试结果来看,600 多个文件串行重构大约需要 8 小时。可以开多个终端并行执行来加速,但工具层面还没做自动并行。
Prompt 策略
重构质量重点依赖模型和 prompt。你可以用通用 prompt 暴力重构所有文件,也可以不断细化场景提高质量。
我采取的策略是分步处理:
- 先将 mixin 转换为 hooks
- 将转换后的 hooks 作为知识库,结合 prompt 处理 .vue 文件
- 最后处理 .js 文件
一个 mixin 转 hooks 的 prompt 示例:
# 任务
将 Vue2 的 mixin 代码转换为 Vue3 组合式 API(hooks)风格
## 要求
- 所有 hooks 通过单独导出,不使用 export default
- hooks 增加 mixinProps 入参,用于接收原 methods 中的方法
- this.$store → vuex4 写法
- this.$emit → Vue3 事件方式
- this.$router → useRouter()
- this.$route → useRoute()
- this.$refs.xxx → vueInstance.refs.xxx
- 其他 this.xxx → vueInstance.setupState.xxx
## 约束
- 多级引用使用可选链
- JSX 语法转为 h() 渲染函数
- 只返回代码文本
一个处理 .vue 文件的 prompt 会更复杂,除了基本转换规则外,还需要把前面生成的 hooks 代码作为"知识库"输入,让 AI 知道 mixin 和 hooks 的对应关系。
AI 的能力边界——那些它搞不定的坑
经过大量测试,我的感受是:大约 9 成的结果在预期内(虽然也会有错误),但有 1 成左右会出现幻觉——就是那种你完全无法理解为什么会这么写的错误。
列举几个需要人工处理的具体场景:
组件名映射丢失:选项式里有些开发者会给组件起别名,AI 不会处理这种情况。
// 选项式
import Test from './Test'
components: { 'ce-shi': Test }
// 转组合式后,没有了 map 过程
// template 中的 <ce-shi /> 将无法识别变量命名冲突:data 和局部变量同名时,转换结果会出问题。
// 选项式
data() { return { value: '' } }
async mounted() {
const value = await fetch('xxx')
this.value = value
}
// 可能被转成
const value = ref('')
onMounted(async () => {
const value = await fetch('xxx')
value.value = value // 💥 变量名冲突
})watch props 写法不对:watch(props.transitions, ...) 应该是 watch(() => props.transitions, ...),AI 经常搞错。
mixin 中未声明的变量:组件用了 this.xxx,但 mixin 和组件里都没声明 xxx,AI 缺少信息会生成离谱的代码。
虽然没有做到生产力的大解放,但相对于前面的传统方案还是有明显优势——执行者不需要通读所有代码,心智负担从"理解并重写"变成了"Debug 并修正"。根据我的主观感受,效率大约提升 50%-60%。
而且很多人工调试场景是有机会通过优化 prompt 进一步减少的。但调试 prompt 本身也有成本,改了 prompt 还可能产生新问题,需要在规划时做好平衡。
一些实操建议
- 最简验证环境:用 vite 建一个 vue3+js 的模板,把项目入口配好,
npx vite就能跑起来,比搭完整脚手架快得多 - 接口代理:用 whistle 或 charles 把某个环境的接口代理到本地,否则没数据没法调
- 先跑 eslint 生成报告:不管选项式还是组合式,先用自动化工具扫一遍语法和引用错误,粗估手动调试的工作量
- 样式别用 AI 重构:不同项目差距太大,建议从组件库入手找依赖更新的差异点,控制好预期
延伸一下思路
基于这套"AI 批量处理 + 人工调试"的模式,其实还可以扩展到其他场景:
- 将 Element 升级到其他组件库
- 对组件库进行大版本升级
- 对项目迭代的 PR 做自动化评审
- 根据 PRD 对需求进行代码迭代
- 各种文本类的批量 AI 处理
写在最后
对一个项目做重构,通常是个非常艰难的决定。业务、产品、开发、测试、项目负责人各有考虑,目标和期望参差不齐。
这次调研这个方案,主要还是想提升重构的效率和稳定性,帮助大家更好地做决策,执行过程中也多一些趣味性,不至于太枯燥。
从现在来看,这套方案仍然无法消除大家对重构的焦虑。但通过它,你至少可以看到自动化完成重构这件事的可能性。路还在走,prompt 还在调,如果你也在做类似的事情,欢迎交流。
延伸阅读:
- gogocode — 基于 AST 的 Vue2 到 Vue3 转换工具
- vue2-to-composition-api — 基于正则的选项式到组合式转换工具