用 AI 半自动化迁移 Vue2 到 Vue3:方案推演与路线选择

最近接到一个老项目的重构诉求——把一个体量不小的 Vue2 项目迁移到 Vue3,顺带把 Element-UI 升级到 Element-Plus。

听起来是个"理论上不难,但谁都不想干"的活。

我参与这个事情的前提是,我想通过 AI 来试试这件事的可能性。所以这篇文章的思路基础是"将 AI 能力最大化",而不是写一个代码转换工具。如果你是来找银弹的,这篇文章可能会让你失望;但如果你也在思考怎么让大型遗留项目的迁移不那么痛苦,希望下面的分析能给你一些参考。

为什么 Vue2 到 Vue3 这么难推动

市面上主流的批量迁移方案就是 gogocode,除此之外我暂时没找到其他特别合适的工具。

在之前的工作经验中,碰到类似场景,项目要么不维护了,要么走到了"用 Vue3 重做一遍"这条路。造成这个局面,我想有几个原因:

  1. 难以说清楚重构的价值。理论上不新增复杂交互的话,Vue2 完全够用。真要加新东西的时候,负责人要么咬咬牙造个轮子撑过去,要么没人愿意因为一个局部的点动全部。
  2. 重构的人大多不是原维护者,往往倾向按自己的风格用 Vue3 重新做一次。
  3. 决策者对重构结果期望过高——认为工作量不大、重构完直接能用、甚至还能顺手加改造。
  4. 过不去测试这一关。测试说没人力回归,重构者又很难负全责。

那渐进式重构呢?"跟着业务迭代一点点来"听起来很美,但实操中也有坑:

  • 过渡期间多维护一套代码,成本增加
  • 架构不好的项目,牵一发动全身
  • 有些包只支持 Vue2,找不到 Vue3 替代品,可能还得自己维护一份
  • 项目越大,这个马拉松跑得越让人疲惫

先说清楚:我下面聊的方案也解决不了以上这些问题。我提供的是工具和思路,不是仙女棒。大家也不要指望未来有什么东西能一键解决所有问题。我只是想提供一些选择,让大家了解自己的处境,尽可能走到让自己舒服的路线上。

传统方案长什么样

为了做对比,先假设一个大家可能第一反应想到的方案(不是推荐方案):

  1. 建一个 Vue3 脚手架
  2. 梳理 Vue2 依赖包的 Vue3 替代品,记录改造方式
  3. 选 1-2 个页面试重构
  4. 和测试沟通用例
  5. 分析页面依赖,确保内容完整
  6. 用 Vue3 重写页面(核心成本点)
  7. 自测
  8. 回归测试
  9. 总结,规划后续
  10. 循环 3-9 直到结束

其中第 6 步是最大的成本——所有和 Vue 相关的代码基本要重写,相当于重新做页面。还有个隐藏成本:万一之前的组件库不支持,还得重写组件库。

补充一下:用 gogocode 会快非常多,只不过 gogocode 的目标是用选项式在 Vue3 跑起来。如果能接受这个结果,也是一条可行的路。

我的思路:让 AI 做粗加工

经过大量尝试,我得出的建议是:AI 做语法转化的粗加工,人工做项目工程化和局部问题处理

形成这个方案的主要工作量,就是"如何让粗加工做得更好"和"人如何高效地处理局部问题"。

用 AI 的优势是——如果处理得足够好,多少个项目都不是问题,而且可以通过不断优化 prompt 来逐渐提升质量。

劣势也很明显——让 AI 达到"足够好"的时间成本不容易估计,不够好的时候人工介入的成本也不好估计。

两条路线:选项式 vs 组合式

在具体方案之前,有几点说在前面:

  1. 方案不是完美的。AI 处理会通过一些冗余和"奇怪的写法"来减少人工介入,代码里会有脏东西。如果你是代码洁癖患者,这篇文章可以到此为止了。
  2. 重构能达到的最好效果就是保持原有项目的现状,不会顺带优化已有的性能和逻辑问题。
  3. 两条路线可以混合使用——比如先走路线一低成本跑起来,再对需要长期迭代的部分用路线二。

路线一:选项式——低成本切到 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 暴力重构所有文件,也可以不断细化场景提高质量。

我采取的策略是分步处理:

  1. 先将 mixin 转换为 hooks
  2. 将转换后的 hooks 作为知识库,结合 prompt 处理 .vue 文件
  3. 最后处理 .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 还在调,如果你也在做类似的事情,欢迎交流。

延伸阅读:

Comments