上个月接到一个老项目的维护需求,代码库还是 TypeScript 2.7 时代的写法,tslint 配置文件比业务代码还长,any 类型用得跟不要钱似的。我原本计划两周搞定,结果光是升级 TS 版本就折腾了快一周——各种废弃 API、类型报错、依赖冲突,改到怀疑人生。后来换了 VS Code 新出的这套 AI 升级工具,整个流程被压缩到了四天,而且大部分工作都是机器替我干的。借着这个项目,我把这套 JS/TS 自动化升级的方案完整梳理了一遍,今天分享出来。
这套工具解决的核心问题很直接:老项目里那些机械性的、重复性的升级工作,比如废弃 API 替换、语法迁移、类型补全、依赖版本对齐,全部交给 AI 来做。它不是帮你重写业务逻辑,而是像带了个熟悉整个 ECMAScript 和 TypeScript 演进历史的高级工程师,帮你把“升级”这件事从“高危操作”变成“可控流程”。适合所有手里攥着老项目、一直不敢动技术债的朋友,也适合刚接手历史代码、想快速摸清家底的开发者。
1. 为什么老项目的 JS/TS 升级这么让人头疼
1.1 老代码的真实状态:不是不能跑,而是不敢碰
我接手的那套系统,距今少说也有六七年了。第一眼看过去,代码还能跑,功能也正常,但整个工程处于一个非常尴尬的状态:package.json 里锁着 typescript@2.7.2,ESLint 还没普及,用的是已经停止维护的 TSLint,模块导入方式还是 import * as React from 'react' 这种老式写法,异步逻辑全是 Promise 链,连一个 async/await 都找不到。
最痛苦的是类型定义。整个项目几千个文件,any 的使用率估计能到 30% 以上。函数入参是 any,返回值是 any,接口定义里全是 [key: string]: any。表面上类型检查能过,但实际上类型系统形同虚设,你根本不知道一个函数会返回什么,只能靠运行时去猜。
这种代码不是“写出来的”,是“长出来的”——经历了多个版本迭代,每个开发者都在原有基础上打补丁。它的问题不在于单个文件有多烂,而在于文件之间的耦合关系像蜘蛛网一样密。你改一个公共工具函数的类型签名,能引发二十多处调用方的连锁报错。这种牵一发动全身的状态,让升级变成了一件“不值得做”的事。
1.2 手动升级的真实痛点:不是不会,而是量太大
很多人觉得升级不就是改几个语法吗?真正上手才知道,工作量完全不在一个量级。
第一类是废弃 API 的替换。比如 Node.js 从 util.promisify 到原生 Promise 的迁移,fs.exists 到 fs.access 的替换,request 库到 axios 或内置 fetch 的切换。这些 API 的废弃往往跨了好几个大版本,中间的迁移路径各不相同,你得自己去翻 changelog。最坑的是有些库的迁移不是一一对应的,而是“一个变两个”或者“两个并一个”,比如 url.parse 拆成了 new URL() 和 urlToHttpOptions,用错了编译都编译不过。
第二类是 TypeScript 版本升级带来的类型检查收紧。从 2.7 升到 3.x,再升到 4.x、5.x,每个大版本都会新增检查规则。最典型的是 strict 模式全面收紧,像 strictNullChecks 打开之后,整个项目里凭空多出来几千个“可能为 null 或 undefined”的报错。手动去补这些类型,一个文件可能要花掉半小时,纯纯的体力活。
第三类是依赖的连带升级。你要升级 TypeScript,就得同步升级 ts-loader 或者 @babel/preset-typescript;升级 React Router,就得跟着改路由写法;升级 Webpack,配置文件可能得重写。一个依赖的升级,往往牵出三四个关联升级,每个都有自己的一套 breaking changes。手动处理就是按葫芦浮起瓢,改完这个挂那个。
1.3 AI 为什么能解决这个问题
这套工具的核心思路,是把“升级”从一个“知识密集型”任务,变成一个“流程密集型”任务。
它的底层逻辑是:老旧项目升级的绝大部分工作,本质上是在处理“已知的、可枚举的、规则明确的历史变更”。TS 官方 deprecation 的 API,有文档;Node.js 废弃的接口,有迁移说明;主流库的大版本变更,有 migration guide。这些知识散落在各处,人工去查效率太低,但 AI 相当于把整个语言和生态的演进史都吃透了。
关键的是,AI 不只是“知道该改成什么”,它还能基于对项目整体结构的理解,给出迁移建议。比如它看到你在用 React.Component 的旧生命周期 componentWillMount,它会建议换成 componentDidMount 或者函数组件的 useEffect;看到你在用 moment,它会建议换成 dayjs 并说明 API 映射关系。它不是机械的查找替换,而是真的理解代码语义。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工具定位与核心设计思路
2.1 不是全部重写,而是增量迁移
这套 AI 升级工具的第一原则,是“只动该动的,不碰不该碰的”。它不会因为你用了旧语法就把整份文件重写一遍,而是以最小侵入的方式,逐点修改。
什么意思?比如你有一个 300 行的工具函数文件,里面绝大部分逻辑都正常,只有两处用了废弃的 fs.exists。这个工具会把这两处的高亮标注出来,给出替换建议,而不是把整个文件丢给 AI 重写。这种做法能最大程度降低回归风险——毕竟机器改 300 行代码,和人工 review 300 行改动,心理压力完全不一样。
这个设计思路对应的是工程上的“可控变更”原则。你的代码库是一步一步长成现在这样的,那也一步一步改回去。每次只改一小块,改了之后立刻编译、立刻测试,出问题也能快速定位到是哪个改动引起的。
2.2 扫描、诊断、改造三层机制
这套工具的工作流程,可以拆成三个独立的层次,每一层的目标都不同。
扫描层负责摸清家底。它会对整个项目做一次全面的静态分析,产出升级报告:哪些文件用了废弃 API,哪些类型是隐式 any,哪些依赖有安全漏洞,哪些语法在目标版本下会报错。这一步的目的是让你心里有数,明确升级范围。
诊断层负责解释“为什么”。每个升级点都会附带说明:这个 API 从哪个版本开始废弃、建议的替代方案是什么、参考的官方文档链接在哪儿。这一步是给人工审核用的,你不需要盲信 AI 的建议,可以自己判断这个改动合不合理。
改造层负责执行。它会在你确认之后,生成具体的 diff 改动,并自动应用。虽然改动是 AI 生成的,但每一处都需要走 Git 的差异对比,你可以逐条审查、逐条合入。整条链路是“先看报告,再查理由,最后动手”,不是 AI 一股脑把所有都给你改了。
2.3 和三方工具链的边界划分
用这套工具之前,我特意理了一下它和其他工具的关系,避免重复工作。
ESLint 是负责“守规矩”的,它检查的是代码风格和常见错误;Prettier 是负责“养眼”的,它统一格式;而这套 AI 升级工具负责的是“跟上时代”——处理的是版本迁移层面的问题。三者虽然有部分重叠,但侧重点完全不同。举个具体例子:ESLint 会提示你 'foo' is defined but never used,但不会帮你把 let foo = 1 改成 const foo = 1 再迁移到新的 std 库用法;而这套工具会做后者。
实际使用中,最优组合是先让 AI 做升级,再用 ESLint 扫一遍,最后 Prettier 统一格式。如果反着来,AI 改完之后你的 ESLint 配置可能又变了,等于白做一遍。
3. 实操:用 AI 完成一次完整的 JS/TS 升级
3.1 安装与前置条件检查
先说门槛。这套工具绑定在 VS Code 生态里,安装路径是扩展市场搜索“JS/TS AI Upgrade”,安装后会在侧边栏出现一个独立的“Upgrade”面板。
安装步骤很简单,但有几个前置条件必须满足,否则工具跑不起来:
- 项目必须有
package.json,且包含完整的依赖清单。工具需要分析你的依赖树,没有它没法定位升级范围。 - 项目建议是 Git 仓库。不是强制,但强烈建议。AI 会直接改代码,没有版本控制做兜底,出了问题你连回滚都做不到。
- TypeScript 版本建议在 2.x 及以上。再老的版本(比如 1.x),AST 解析都可能出错,工具直接罢工。
- 网络连接要通畅。AI 分析配置是在线的,离线环境下只有静态扫描功能可用。
3.2 启动项目扫描,生成升级报告
安装完成后,在 Upgrade 面板点击“Scan Project”,工具会开始全项目扫描。扫描时间取决于代码量,我第一次扫一个约 800 文件的中型工程,用了大概 3 分钟。
扫描完成后会自动生成一份升级报告,它把问题分成了几类:
| 分类 | 含义 | 升级优先级 |
|---|---|---|
| 废弃 API | 使用了已废弃或已移除的 API | 高 |
| 类型缺陷 | 隐式 any、缺失类型声明 | 中 |
| 依赖过期 | 存在安全漏洞或过度滞后的依赖 | 高 |
| 语法兼容 | 目标版本不兼容的语法写法 | 中 |
| 架构建议 | 非强制但推荐升级的模式 | 低 |
报告里每一项都能展开看详情。比如“废弃 API”大类里,会列出具体文件路径、行号、废弃 API 的名称、被废弃的版本号,以及建议的替代写法。这块的价值远超我预期——之前我都是自己去翻 changelog 对号入座,现在省了至少一个下午。
3.3 让 AI 开始工作:单文件试用与批量处理
拿到报告之后,我建议你先别急着全量处理,而是先选一个文件试跑。
我当时挑了一个不起眼的工具函数文件,大概 200 行,里面用了 fs.existsSync 和一个老式的 request-promise 调用。在报告里点开这个文件,点击“AI Upgrade This File”,工具会弹出一个 diff 预览界面,左侧是原代码,右侧是 AI 改造后的代码,两者差异高亮显示。
改造后的代码让我眼前一亮。它不只是简单把 fs.existsSync 换成 fs.accessSync,而是把整段逻辑用更现代的方式重写了:
typescript复制// 改造前
import * as fs from 'fs';
if (fs.existsSync('/path/to/file')) {
// 处理文件
}
// 改造后
import { access } from 'fs/promises';
try {
await access('/path/to/file');
// 处理文件
} catch {
// 文件不存在
}
这个改动逻辑上是正确的——fs.access 比 existsSync 更优雅,也更符合现代 Node.js 推荐的错误处理方式。但它把同步逻辑改成了异步,这个认知是“有深度”的,不是简单文本替换能实现的。
看完单文件效果后,我放心地启用了“Batch Upgrade”模式,但设置了一个限制:一次只处理 20 个文件,处理完后停一下,确认没问题再继续。
3.4 人工审查的核心环节
AI 改完代码之后,最重要的一步是人工审查。
我的审查流程是三层过滤。第一层看 diff 的规模和类别,如果一个文件被改动了超过 40% 的行数,我会直接跳过,单独拎出来手改——改动面太大说明 AI 可能理解歪了;第二层跑编译和测试,这是最硬的校验标准,编译过不去或者单测挂了,不管 AI 改得多完美都要重新来;第三层是抽验核心逻辑文件,比如路由注册、数据库操作的封装层,这些地方出问题影响面太大,必须逐行看。
这里有一个很重要的心理建设:AI 的定位是帮你处理量大的机械性工作,不是替代你做判断。它把 100 个文件里 1000 处重复的 API 替换干完了,你只需要重点看那 5 个改动逻辑复杂的文件,这个时间分配才是健康的。
3.5 依赖版本与配置文件的联动升级
代码文件改完之后,工具的第二步是把目光投向 package.json。它会在报告中给出建议升级的依赖清单,区分“必须升级”和“可以顺手升级”两类。
我那个项目里,typescript 从 2.7.2 升到 4.9.5,ts-node 从 3.x 升到 10.x,@types/node 全面对齐,eslint 从 5.x 升到 8.x。工具会给出每个依赖的最低目标版本,以及升级后必需的配置改动说明。比如 TypeScript 从 3.x 升到 4.x 之后,tsconfig.json 里 module 和 target 的推荐值变了,工具会直接给出一份修改后的 tsconfig.json 作为建议。
这部分实操里最需要注意的是 engines 字段。如果你的项目要部署到老的 Node 运行时环境,依赖升级可能带来运行时兼容问题。工具对这种场景会非常保守地在报告里标注“需要人工确认运行环境”,这个细节很良心,避免了升级完代码上线跑不起来的惨剧。
4. 常见问题与排查技巧实录
4.1 AI 改完代码编译不过,怎么办
这是我最常遇到的问题,尤其是批量处理模式。AI 改了 5 个文件,单个文件都能编译过,但合在一起就挂了。原因通常是跨文件的类型依赖——A 文件改了一个函数的返回类型,B 文件还在按旧类型调用。
处理思路很简单:小范围回滚,不要硬刚。先用 git checkout -- <file> 把 B 文件还原到改动前,确认编译通过后,只保留 A 文件的改动。然后单独处理 B 文件,手动修改调用处的类型。不要尝试让 AI 一次性解决跨文件问题,它的上下文窗口有限,处理单文件是它的舒适区。
另一个小技巧是,批量处理时按依赖方向排序。先改底层工具函数,再改上层业务代码,这样类型不匹配的问题会少很多。工具本身没有提供这个排序功能,但我实测下来人工排序一下,编译错误能减少一半以上。
4.2 怎么识别 AI 有没有把业务逻辑改歪
这是最让人放心不下的事。AI 能力再强,它也不懂你的业务。改语法没问题,但如果它“自作聪明”地把逻辑顺序也调整了,那风险就大了。
我的经验是看两类 diff。第一类是“注释被删了”,AI 经常会把注释当成无用信息清掉,但很多业务注释解释的都是当时为什么这么写、有什么坑,删了就丢了上下文;第二类是“代码顺序变了”,如果 AI 调整了 if 分支的顺序、交换了两个表达式的求值位置,我基本会直接还原。
一个更实用的防呆手段,是在给 AI 发指令时主动加约束。工具支持自定义提示词模板,我会在模板里加一行“仅做语法和 API 迁移,禁止改变任何逻辑顺序和分支结构”。这样 AI 会收敛很多,改出来的东西也更可控。
4.3 第三方依赖升级的联动坑
老项目的第三方依赖是最容易翻车的地方。表面上 AI 把代码都改成新写法了,但如果你不把对应的依赖包升到新版本,运行时照样炸。
有一类特别隐蔽:工具包版本号没变,但间接依赖变了。比如你升了 typescript,但 ts-loader 还在用 8.x 的老版本,构建时直接报错 TypeScript compiler version mismatch。或者你升级了 jest,但 babel-jest 没跟着升,测试直接跑不起来。
解决这类问题的方法只有一个:升级依赖时一定要看 peerDependencies。工具的报告里会标注每个依赖的建议版本,但不会帮你处理间接依赖。我在实际操作中踩了两次这个坑之后,就养成习惯——每次升级完跑一遍 npm install,然后立刻跑一次构建和测试,问题早暴露早解决。
4.4 老框架升级不了,怎么办
有的项目里会有一些特别老的框架,比如 AngularJS(不是 Angular),或者 jQuery 插件体系。这类框架本身已经停止维护,AI 不会给出“升级路径”,因为根本没有。
遇到这种情况,我的建议是设置“隔离区”。用 tsconfig.json 的 exclude 字段,把这类老文件从类型检查范围里剔除,或者单独维护一个 stub.d.ts 声明文件,让编译能通过。工具支持在报告里标记“不处理此文件”,后续 AI 升级时会自动跳过这些文件。这不是治本之策,但至少能让整个项目的升级流程不至于被几个老顽固拖死。
4.5 提升 AI 升级效率的提速技巧
如果项目很大,一次全量扫描生成几百页报告,AI 处理起来会很慢。我用了几轮以后,总结出了几个提速技巧:
- 按目录分批处理:工具支持在扫描时指定目录范围,我一般从
src/utils和src/lib这类底层目录开始,再往上处理业务代码。 - 利用
.aiignore文件:把不需要升级的目录(比如vendor、dist、node_modules)加进去,扫描速度会快很多。 - 先处理高优先级问题:报告里优先级的排序是有讲究的——废弃 API 和高危依赖先处理,语法兼容和架构建议可以放着慢慢来。优先级低的问题不影响编译,风险也低,不需要一次性清完。
5. 工具链配合建议
5.1 和 VS Code 内置重构功能的互补
这套 AI 工具和 VS Code 自带的重构功能并不冲突,配合得当可以发挥 1+1>2 的效果。
比如 AI 改完之后,发现某个文件里存在大量重复的代码片段,它会提示“建议抽成公共函数”,但不会主动做。这时候我用 VS Code 的“Extract to function in new file”重构快捷键,几秒钟就能完成抽取。再比如重命名变量和函数,AI 不会顺手帮你把全局的引用全部改掉,但 VS Code 内置的 F2 重命名可以。
我的使用顺序一般是:AI 先做版本升级和 API 替换,然后我用内置重构处理代码结构优化。先改“版本”,再改“结构”,每一步的改动面都控制在可审查范围内。
5.2 与 ESLint、CI 流水线的衔接
升级完代码之后,你还需要做一件事:更新 ESLint 配置,让它和新的代码风格对齐。
我实际操作中是一套组合拳:AI 升级 → 手动处理编译错误 → 更新 ESLint 和 Prettier 配置 → 全局跑一遍 eslint --fix → 提交代码。这里有个小细节,升级之后老项目的 ESLint 配置里往往有大量“忽略规则”和“禁用规则”,这些规则在升级完后已经过时了,需要清理。否则会出现一种搞笑的情况:代码已经是最新写法了,但 ESLint 还在用 2017 年的规则集在检查,两边不匹配。
CI 流水线方面,我建议在升级 PR 里额外加一个检查项:tsc --noEmit。只跑单测还不够,类型检查能拦截绝大多数回归问题。AI 升级完成后,全量类型检查 + 全量单测 + lint 三项全绿,这个 PR 基本就是安全的。
5.3 团队协作与知识沉淀
最后说一个容易被忽略的点——AI 升级不是一次性的,而是一个持续过程。
你这次升级完,不代表项目就永远健康了。框架和语言还会继续演进,代码还会继续腐化。我的做法是把这套工具纳入团队的例行工作流:每个月固定跑一次扫描,看看有多少新增的废弃 API 和依赖漏洞,小问题当月就处理掉。这才是一个可持续的状态。
另外,AI 升级时产出的报告和建议,本身就是非常有价值的知识沉淀。我会把关键问题的修复方案整理成团队的内部文档,这样下次遇到类似问题,不用再依赖工具,成员自己就能处理。毕竟工具是辅助,团队的能力成长才是根本。
从我个人的实操体会来说,这套 AI 升级工具最打动我的不是它多聪明,而是它把“升级老项目”的门槛降下来了。以前升级 TypeScript 大版本,我一个人要花好几天,现在配合 AI 一天就能搞定大部分机械工作,省下来的时间全花在了真正需要人判断的业务逻辑审查上。这种“机器干体力活,人干脑力活”的分工,才是 AI 辅助开发的正确打开方式。
