框架 + 语言迁移工具集
给正在做真实迁移的工程师的十个选择:JS→TS、CommonJS→ESM、React class→hooks、Express→Hono、Python 2→3、Vue 2→3。Codemod CLI + ast-grep 结构化重写 + TypeScript / TypeScript-Go 加速类型检查 + tsx 免构建跑 TS + Mypy / Ty 给 Python 加类型 + Ruff 跑 pyupgrade 规则 + Hono 作为 Express 迁移目标 + Legacy Modernizer agent。按顺序装,按片切。
这个 pack 解决什么
真正的迁移 — JavaScript → TypeScript、CommonJS → ESM、React class 组件 → hooks、Express → Hono、Python 2 → Python 3、Vue 2 → Vue 3 — 失败几乎都是同一个原因:有人想在一个巨型 branch 里一次性做完。六周后,那个 branch 改了 800 个文件,每次 main 合并都冲突,谁也不敢把它上线。
这个 pack 是 AI 时代真把一次迁移做完的方式。无论源/目标是什么,模式都一样:基线 → codemod → 类型检查 → 测试 → 上线一片 → 再来一片。下面的工具让每一步几分钟就能跑完,而不是几天,于是每片可以小到午饭前合掉。
推荐安装顺序
- Codemod — AI-Powered Code Migration CLI (从这里开始)。整套的中心。把大规模代码迁移脚手架成多步 YAML 工作流,可分享可复用。一等公民支持 ast-grep,社区注册表里有现成的预制 codemod(React class→hooks、Mocha→Jest、CommonJS→ESM 等等),注册表里没有的还能用 AI 协助写。
- ast-grep — Structural Code Search and Rewrite Tool。绝大多数现代 codemod 底下的真正重写引擎。按 AST 形状匹配模式而不是正则 —
$VAR.then($CB)能匹配你要改成 await 的所有 promise 回调,不管空格和变量名。可以单独用做一次性改写,也可以作为 Codemod CLI 的后端。 - TypeScript — JavaScript with Syntax for Types。JS→TS 迁移的目的地。一开始
--noImplicitAny别打开,按目录一个个迁过去再打开。 - TypeScript Go — Native TypeScript Compiler Port in Go。
tsc的直接替代,类型检查快约 10 倍。在迁移过程中是关键,因为你每跑完一批 codemod 就要重新跑一次类型检查 — 2 分钟的 tsc 变成 12 秒的环,而 12 秒就是「保持心流」和「切去看 Slack」的分界。 - tsx — The Fastest Way to Run TypeScript in Node.js。让你迁移期间直接执行
.ts文件,不需要构建步骤。当一半代码是 JS、一半是 TS,又不想搭两套并行构建管道时至关重要。 - Mypy — Optional Static Type Checker for Python。Python 版的「按目录打开类型检查」。按模块用
--strict,不要整个项目同时开。配上py.typed标记,下游消费者就能看到你的类型。 - Ruff — Python Linter and Formatter in One Fast CLI。专门给 Python 2→3 迁移用:Ruff 的
pyupgrade规则集会自动修绝大多数现代化模式:print x→print(x)、u"str"→"str"、super(Foo, self)→super()、dict.iteritems()→dict.items()。一次ruff check --select UP --fix就能处理掉几千个机械改动。 - Ty — Astral's Fast Python Type Checker。Mypy 的 Rust 替代品,和 Ruff 同一拨人做的。值得并行试用 — 反馈延迟从 8 秒降到 200ms 之后,你才会真的保持边写边跑。
- Hono — Ultrafast Web Framework for the Edge。从 Express 迁出去的目的地。API 表面足够接近,handler 一个一个迁就行:
req.body→await c.req.json()、res.json(x)→c.json(x),中间件签名也相似。加分项:能跑在 Cloudflare/Bun/Node 上,迁完的路由想放哪都可以。 - Claude Code Agent: Legacy Modernizer。codemod 搞不定那部分的兜底:废弃的配置、没人维护的模块、自从 IE 11 后就再没触发过的 version-sniff 分支。Agent 读代码、给出拆除顺序、写 PR。用它干那 20% 需要判断的活,别让它干 80% 的机械活。
它们怎么协同
┌──────────────────────────────────┐
│ Codemod CLI (编排者) │
│ └─ ast-grep (重写引擎) │
└──────────────────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
JS → TS Python 2 → 3 遗留考古
───────── ────────── ──────────────
TypeScript Ruff (--select UP) Legacy Modernizer
TypeScript-Go Mypy / Ty (agent)
tsx (跑 TS)
┌──────────────┐
│ Express→Hono │
│ Hono │
└──────────────┘
│
▼
一片切片 1-3 个 PR 上线
每一片要跑的迁移环:
- 基线 — 选下一组约 20-50 个文件。快照测试输出和 bundle 大小。
- Codemod — 只对这一片跑 Codemod CLI(或直接 ast-grep)。检查 diff。
- 类型检查 — TypeScript-Go / Ty 开 watch 模式。修 codemod 推不出来的错误。
- 测试 — 对这一片跑现有测试套件。tsx 让你不用重新构建就能做。
- 上线 — 开 PR、合掉、上线。明天换下一片。
如果任何一片超过 2 天还没合,那片太大。切。
你会遇到的取舍
- Codemod CLI vs 手写 jscodeshift — Codemod CLI 包装了 ast-grep 和注册表。手写 jscodeshift 给你完整的 JS API 控制。先用 Codemod CLI;只有当注册表没有你要的转换、且 ast-grep 模式也表达不了时,才往下掉到 jscodeshift。
- Mypy vs Ty — Mypy 是老牌,更成熟,更慢。Ty 快到可以一直开在编辑器里不卡。迁移过程中两个都跑:Mypy 在 CI 里保彻底,Ty 在本地保速度。
- TypeScript-Go vs tsc — TypeScript-Go 是微软官方移植,更快但还没完全特性对齐。迁移时用它跑内环类型检查;CI 里继续用 tsc 作为权威,直到对齐版本上线。
- Express→Hono vs Express→Fastify — Hono 跑在边缘运行时(Cloudflare Workers、Bun、Deno)也跑在 Node,handler API 几乎一样。Fastify 只跑 Node,但在传统服务器上更成熟。要往边缘现代化就选 Hono;要留在 Node 用最成熟的就选 Fastify。
- 全靠 codemod vs Legacy Modernizer — codemod 几分钟内干掉 80% 机械工作。Legacy Modernizer agent 处理需要判断的 20%(死代码、被弃功能、条件分支)。别让 agent 干机械活;别让 codemod 干考古活。
常见踩坑
- codemod 切片太大 — 「把 800 个文件一次全转 TS」保证那个 PR 永远合不掉。按目录迁,不按语言整体迁。每个 PR 都要小到一次坐下来能 review 完。
- 忘了类型检查环很慢 — 每跑完一批 codemod 就跑
tsc --noEmit是杀掉你一天的那一步。第一天就装 TypeScript-Go。10 倍提速的差别就是这事到底有趣还是苦差。 - 盲抄注册表 codemod — 注册表 codemod 是起点不是圣经。打开 YAML,读模式,先跑一个文件,目视检查 diff 后再放出去打全库。
- 第一天就 Mypy
--strict全开 — 放弃最快的方式。按模块在[mypy-yourpackage.submodule]块里启用,向外扩。 - Express→Hono 一次性大改写 — 路由分组迁,两个 server 跑在同一个 router 后面(Hono 挂在
/v2下),endpoint 一个个切。别把整个 app 一次性翻。 - 盲信 codemod 处理测试 — codemod 会很高兴地把一个坏的测试套件「迁移」成一个还是坏的测试套件。每批 codemod 后跑一次测试。如果迁之前绿,迁之后红,那就是 codemod 漏了什么 — 这个 bug 你想在那一刻就抓到,不是三周后线上才发现。
10 个资产打包就绪
常见问题
每片迁移应该多大?
小到一个 PR 30 分钟能 review 完、当天能上线。实际上通常就是单个目录,20-50 个文件,净 diff 不超过约 1000 行。如果某片超过这个量级,拆。增量迁移的全部意义就在于:你随时可以停下来,把已经做好的上线,明天再继续,而代码库不会处于半坏状态。
已经直接用 ast-grep 了,还需要 Codemod CLI 吗?
不严格需要,但 Codemod CLI 值两个理由。第一,社区注册表里有常见迁移的预制 codemod(React class→hooks、Mocha→Jest、CommonJS→ESM),不然你得自己写。第二,YAML 工作流格式让你把多步 ast-grep 加 shell 步骤(跑测试、格式化、commit)串成一个可复现单元。一次性改写就单独用 ast-grep;迁移超过 2-3 步或要和队友共享迁移流程时,用 Codemod CLI。
Python 迁移为什么 Mypy 和 Ty 都要?
Mypy 是事实标准,插件/stub 生态最大 — 你想要它在 CI 里作为权威。Ty 显著更快(Rust 底层),在编辑器里几乎瞬时 — 你想要它在迁移内环用。它们检查同样的 # type: 注解,所以同时跑不需要改任何代码。这个分工纯粹是反馈延迟的考虑:Mypy 保正确性,Ty 保速度。
测试还没写齐的迁移怎么做?
先加一层薄的特征化测试(characterization test),再做迁移。挑公开边界(HTTP 路由、CLI 命令、导出函数),写测试锁住当前行为 — 哪怕是有 bug 的行为 — 然后在迁移期间跑这些测试。codemod 是机械的,它会忠实地保留 bug。没有特征化测试,一次 codemod 后行为变了你分不清是 codemod 引入了 bug 还是它保留了 bug。在迁移片的前一片把测试加上。
Legacy Modernizer agent 和写 codemod 怎么选?
codemod 适合能用模式描述的转换:「每个 var x = 改成 let x =」「每个 .then(cb) 改成 await」。Legacy Modernizer agent 适合需要上下文判断的决定:「这个 2018 年的 feature flag 死了,移除」「这个模块全库没人引用,删掉」「这个条件路径只在 IE 11 跑,剪掉」。规则能用 ast-grep 模式写出来就用 codemod;规则是「读代码再判断」就用 agent。混用两边的活只会两边都浪费。