现象
AI 在批量修改前端路由文件时,文件内容被替换为一个标准的 TanStack Router “Hello” 模板:
| |
每次出现时内容高度一致,不是乱码也不是局部损坏,而是一个语法完整、格式规范的路由骨架页。现象可稳定复现。
询问不同的ai大模型和询问朋友 给答案都不同。于是开始在本地ai的帮助下开始排查。
排查过程
方向一:编辑器或 AI 工具的 bug
最初怀疑是 AI 生成代码时出了偏差。但排查后发现:
- AI 工具的编辑操作是精确的文本替换(
oldText→newText),没有写入模板的逻辑 - 模板内容和 AI 的本次编辑任务无关
- 单独提交单处编辑时不会触发,只有批量编辑时才出现
这说明模板不是 AI 工具的产出,而是某个中间环节注入的。
方向二:git 操作或自动提交
检查了 git 的 hooks 目录,所有 hook 都是 .sample 后缀,没有启用的 hook。git config 中也没有关联任何 hook 或自动化脚本。排除。
方向三:框架的自动脚手架
查看项目的依赖,发现使用了 @tanstack/router-plugin + @tanstack/router-generator。前者是 Vite 插件,后者负责文件路由的代码生成。
关键线索在这个文件:
| |
| |
这正是出现在被覆盖文件中的模板。填入路径参数后生成的内容完全匹配。
触发条件
在 generator.js 中找到脚手架的执行入口:
| |
逻辑很直接:如果文件内容为空,就用模板填充并写回。这是一个为"用户新建空路由文件时自动填充骨架"而设计的特性。
同时 router-generator-plugin.js 注册了文件变更监听:
| |
Vite dev server 运行时,Generator 监听 ./src/routes/ 目录下所有文件的变动,一旦触发就读取文件、判断是否为空、决定要不要脚手架覆盖。
竞态条件
结合 AI 工具的工作方式,完整的发生链路是:
- AI 工具对路由文件执行批量文本编辑(多处替换)
- 文件系统层面上,写入不是原子的——存在一个时间窗口文件内容为空或为部分内容
- Generator 的
watchChange在此时触发,读取文件发现为空 if (!existingRouteFile.fileContent)判断为真- Generator 生成模板并通过
safeFileWrite写入(写到临时文件后rename,原子操作) - 模板覆盖了 AI 工具的编辑结果
safeFileWrite 的实现使用了写临时文件再 rename 的策略:
| |
rename 是 POSIX 原子操作,一旦 Generator 率先完成,模板就固化了。
设计理念的矛盾
这是一个设计理念层面的冲突,不是简单的 bug。
TanStack Router Generator 做了一个假设:空的路由文件 = 用户刚创建的文件,需要脚手架帮填骨架。这个假设在以下场景成立:
- 开发者手动
touch一个新路由文件 - 开发者用 IDE 新建文件后尚未输入内容
但在 AI 辅助开发的工作流中,这个假设不成立:
- AI 编辑文件时,文件内容在短时间内经历"完整 → 空 → 完整"的状态变化
- AI 有时会删除文件后重新创建(而不是原地编辑),这同样会产生空文件窗口
- 批量操作涉及多个文件时,时间窗口被放大
问题的本质是:Generator 把"文件内容为空"等同于"文件需要脚手架",但"空"也可能是一个正在被写入的中间状态。 对于人工操作,这个时间窗口短到几乎不存在(毫秒级),但在自动化工具的操作下,窗口被显著放大。
为什么关不掉
查看了完整的配置接口:
enableRouteGeneration: false可以关闭整个 Generator,但也会禁用路由树生成(routeTree.gen.ts),加新路由后需要手动维护customScaffolding.routeTemplate只能替换模板内容,不能禁止脚手架行为- 没有独立的选项来关闭"空文件自动填充"这个特性
唯一的官方配置路径是 routeFileIgnorePattern 来忽略特定文件,但这需要预先知道哪些文件会被编辑,对 AI 工作流不适用。
代码证据汇总
三个关键文件(版本 [email protected]、[email protected]):
| 文件 | 行号 | 作用 |
|---|---|---|
router-generator/dist/esm/template.js | route.template | 定义脚手架模板(Hello 页面的来源) |
router-generator/dist/esm/generator.js | 471 | if (!existingRouteFile.fileContent) 空文件判定 |
router-plugin/dist/esm/router-generator-plugin.js | 48 | watchChange 文件变更监听入口 |
后续
截至本文,上游 npm 包的最新版本与本地版本一致,没有改善和修复。
最后的解决方案 就是迁移到 更为成熟稳健的 React Router v7 .但是考虑到 TanStack Query 还是较为稳健的,暂时保留。 SWR 还需要观望。