291 lines
13 KiB
Markdown
291 lines
13 KiB
Markdown
# 工程优化审查报告(2026-03-30)
|
||
|
||
## 审查范围
|
||
|
||
- 扫描范围:`src/`、`scripts/`、`docs/`、`.github/`、`package.json`、`tsconfig.json`、`vite.config.ts`
|
||
- 实际执行:`npm run lint`、`npm run test`、`npm run build`、`npm run check:content`
|
||
- 说明:按仓库要求,本报告不讨论中文乱码问题,只讨论工程结构、边界、质量门禁、可维护性和后续扩展成本
|
||
|
||
## 先说结论
|
||
|
||
这轮代码库相较 `docs/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md` 已经有明显进展,项目不再是“所有能力都糊在一个入口文件里”的状态了,但整体仍然处于“重构过渡期”。
|
||
|
||
已经落地的积极变化:
|
||
|
||
- 入口路由已经从手写 `pathname` 分发,收敛到 `src/main.tsx` + `src/routing/appRoutes.tsx`
|
||
- 持久化能力已经抽到 `src/persistence/`
|
||
- 编辑器公共能力已经出现 `src/editor/shared/`
|
||
- `CI + ESLint + Prettier + Vitest` 已经接入
|
||
- 本地 API 插件已经从 `vite.config.ts` 抽走,落到 `scripts/dev-server/localApiPlugins.ts`
|
||
- `preview` 环境里的 JSON 写入接口已经改成只读,这一点比上轮更安全
|
||
|
||
但当前仍然存在 5 个值得优先处理的工程问题:
|
||
|
||
1. 运行时主链仍然过于集中,`story/combat` 的真实边界还没有彻底拆开
|
||
2. `src/services/ai.ts` 仍处于迁移中间态,存在重复实现和旧逻辑残留
|
||
3. 编辑器主入口仍是大型聚合组件,迁移残留没有清干净
|
||
4. 质量门禁已经有框架,但还不够“硬”,warning 和测试覆盖缺口仍然明显
|
||
5. 运行时渲染层和构建体积仍偏重,重 UI 模块还没拆到合适粒度
|
||
|
||
## 当前运行状态
|
||
|
||
- `npm run test` 通过,6 个测试文件共 18 个测试全部通过
|
||
- `npm run build` 通过
|
||
- `npm run check:content` 通过
|
||
- `npm run lint` 通过,但仍有 76 条 warning
|
||
|
||
当前构建产物里仍然存在较重 chunk:
|
||
|
||
- `dist/assets/GameCanvas-*.js` 约 `346.58 kB`
|
||
- `dist/assets/App-*.js` 约 `326.89 kB`
|
||
- `dist/assets/index-*.js` 约 `197.80 kB`
|
||
- `dist/assets/index-*.css` 约 `117.37 kB`
|
||
|
||
## P0:运行时主链仍然过于集中,Story/Combat 边界还没有拆透
|
||
|
||
### 现状
|
||
|
||
虽然 `App.tsx` 已经明显瘦身,`GameShell` 也比之前更像壳层,但真正决定游戏推进的主逻辑仍然高度集中在两个大 hook 里:
|
||
|
||
- `src/hooks/useStoryGeneration.ts:824`
|
||
- `src/hooks/useCombatFlow.ts:382`
|
||
|
||
### 证据
|
||
|
||
`useStoryGeneration` 仍然同时编排了多个本应继续拆开的子领域:
|
||
|
||
- `src/hooks/useStoryGeneration.ts:852` 接入 `useCharacterChatFlow`
|
||
- `src/hooks/useStoryGeneration.ts:1583` 接入 `useTreasureFlow`
|
||
- `src/hooks/useStoryGeneration.ts:1588` 接入 `useInventoryFlow`
|
||
- `src/hooks/useStoryGeneration.ts:1593` 接入 `useEquipmentFlow`
|
||
- `src/hooks/useStoryGeneration.ts:1597` 接入 `useForgeFlow`
|
||
- 文件总长仍有约 `3240` 行
|
||
- 结尾返回对象同时暴露剧情推进、地图旅行、NPC 交易/送礼/招募、角色聊天、背包与锻造 UI 能力,典型位置在 `src/hooks/useStoryGeneration.ts:3171-3219`
|
||
|
||
`useCombatFlow` 也不是纯计算层,它仍然同时承担:
|
||
|
||
- 战斗前后状态推导
|
||
- 动画播放与时间推进
|
||
- `setGameState` 驱动的可视化编排
|
||
- 逃跑流程与 story 响应同步
|
||
|
||
关键位置:
|
||
|
||
- `src/hooks/useCombatFlow.ts:382` `useCombatFlow`
|
||
- `src/hooks/useCombatFlow.ts:1195` `playEscapeSequenceWithStorySync`
|
||
|
||
### 影响
|
||
|
||
- 任何一个“剧情选项新增”都很容易同时碰到 battle、npc、quest、inventory、chat 五条链路
|
||
- review 成本高,回归范围判断依赖人脑上下文
|
||
- 单测很难往 hook 级别补,因为副作用、异步节奏和 UI 状态混在一起
|
||
- 后续想继续做 camp、custom world、更多 companion 玩法时,改动会继续集中到这两个入口
|
||
|
||
### 建议
|
||
|
||
- 把 `useStoryGeneration` 继续下钻成“剧情推进 orchestrator + 领域 action service”
|
||
- `useStoryGeneration` 自己只保留编排,不再直接维护 trade/gift/recruit/chat/inventory/forge 的全部细节
|
||
- `useCombatFlow` 继续向“纯战斗结算”和“播放适配层”分离
|
||
- 先稳定公开接口,再做内部拆分,避免一次性大改
|
||
|
||
## P1:AI 服务迁移只完成了一半,`src/services/ai.ts` 仍然存在双轨实现
|
||
|
||
### 现状
|
||
|
||
仓库已经新增了:
|
||
|
||
- `src/services/llmClient.ts`
|
||
- `src/services/llmParsers.ts`
|
||
- `src/services/aiFallbacks.ts`
|
||
- `src/services/aiTypes.ts`
|
||
|
||
这说明拆层方向是对的。但 `src/services/ai.ts` 还没有真正变成“纯 orchestration 层”,里面仍然保留着一整套旧 transport / parse / fallback 逻辑。
|
||
|
||
### 证据
|
||
|
||
- `src/services/ai.ts:64-66` 已经开始导入 `llmClient`
|
||
- `src/services/ai.ts:89-95` 仍然保留本地 `resolveTimeoutMs` 和超时常量
|
||
- `src/services/ai.ts:647` 仍然保留 `_requestPlainTextCompletion`
|
||
- `src/services/ai.ts:719` 仍然保留 `_parseJsonResponseText`
|
||
- `src/services/ai.ts:739` 仍然保留 `_parseLineListContent`
|
||
- `src/services/ai.ts:784` 仍然保留 `_streamPlainTextCompletion`
|
||
- `src/services/ai.ts:885-904` 仍然保留一批旧的 `_buildOffline...` helper
|
||
|
||
与之对应,新的实现已经在下面这些文件里存在:
|
||
|
||
- `src/services/llmClient.ts`
|
||
- `src/services/llmParsers.ts`
|
||
|
||
### 影响
|
||
|
||
- 同一类能力现在有两套真相源,后续改错误分类、超时策略、SSE 行为时容易漏改
|
||
- 新同学读代码时很难判断应该继续改 `ai.ts`,还是应该去改 `llmClient.ts`
|
||
- 迁移残留会拉高维护成本,也会让测试边界变得模糊
|
||
|
||
### 建议
|
||
|
||
- 把 `src/services/ai.ts` 收敛成“业务 prompt 编排 + fallback 选择”层
|
||
- 彻底删掉未再需要的 `_requestPlainTextCompletion`、`_streamPlainTextCompletion`、`_parse*` 等旧 helper
|
||
- 所有 transport / timeout / connectivity error / SSE 解析都只保留在 `llmClient.ts` 和 `llmParsers.ts`
|
||
- 迁移完成后,给 `ai.ts` 增加一组 orchestration 级测试,防止 fallback 分支回归
|
||
|
||
## P1:编辑器主入口仍然太重,而且过渡态残留还在
|
||
|
||
### 现状
|
||
|
||
编辑器公共能力已经开始沉淀到 `src/editor/shared/`,这是好事;但主编辑器入口仍然比较重,且部分文件还保留着迁移过程里的死代码和注释块。
|
||
|
||
### 证据
|
||
|
||
`PresetEditor` 仍然是一个大型聚合组件:
|
||
|
||
- `src/components/PresetEditor.tsx:402` `CharacterPresetPanel`
|
||
- `src/components/PresetEditor.tsx:1174` `SceneNpcPresetPanel`
|
||
- `src/components/PresetEditor.tsx:1547` `ScenePresetPanel`
|
||
- `src/components/PresetEditor.tsx:1852` `MonsterPresetPanel`
|
||
- `src/components/PresetEditor.tsx:2218` `PresetEditor`
|
||
- 文件总长仍有约 `2279` 行
|
||
|
||
同时,文件里还留着明显的过渡态残留:
|
||
|
||
- `src/components/PresetEditor.tsx:227` 仍然保留未使用的 `_SectionCard`
|
||
- `src/components/NpcVisualEditor.tsx:684` 保留 `if (false)` 的旧保存路径
|
||
- `src/components/NpcVisualEditor.tsx:685` 明确写着 “Deprecated inline save path kept only until the shared client migration is cleaned up.”
|
||
- `src/components/NpcVisualEditor.tsx:724` 还有第二处 `if (false)` 残留
|
||
|
||
### 影响
|
||
|
||
- 编辑器后续继续扩展时,容易重新长回“大一统文件”
|
||
- 过渡代码会误导维护者,以为旧保存链路仍然有效
|
||
- 公共层虽然建起来了,但如果不清理旧代码,长期会形成“共享层 + 本地特例”并存
|
||
|
||
### 建议
|
||
|
||
- 以“一个 tab 一个容器”的方式,把 `PresetEditor` 再拆一层
|
||
- 清理 `NpcVisualEditor` 里的废弃代码块,不要再保留 `if (false)` 分支
|
||
- 对编辑器共享层设定明确规则:保存请求、克隆、Section 容器、错误提示都必须走 shared
|
||
- 对编辑器做一次“小型迁移完成清扫”,优先删掉已经废弃但还挂在文件里的旧路径
|
||
|
||
## P1:质量门禁已经接上,但还不够硬
|
||
|
||
### 现状
|
||
|
||
基础设施已经比上轮完整很多,但当前门禁仍然偏“有检查,不够严格”。
|
||
|
||
### 证据
|
||
|
||
当前 lint 结果:
|
||
|
||
- 本次 `npm run lint` 实际输出 `76` 条 warning,虽然命令返回成功
|
||
|
||
脚本和规则层面的原因也很明确:
|
||
|
||
- `package.json:12` 的 `lint` 仍然是 `eslint . ... && tsc --noEmit`,没有 `--max-warnings 0`
|
||
- `package.json:11` 的 `lint:guardrails` 虽然加了 `--max-warnings 0`,但它只覆盖一组显式 allowlist 文件
|
||
- `package.json:18` 的 `check` 会先跑 `lint:guardrails`,再跑宽松版 `lint`
|
||
- `.eslintrc.cjs:45-61` 里大量规则仍然是 `warn`
|
||
- `.github/workflows/ci.yml:28-40` 已经把 `lint / guardrails / test / build / check:content` 都接到 CI,但 warning 仍能稳定进主干
|
||
|
||
测试覆盖也还是偏薄:
|
||
|
||
- `src/` 当前共有 `126` 个文件
|
||
- 其中测试文件只有 `6` 个
|
||
- 现有测试主要覆盖 `routing`、`persistence`、`jsonClient`、`llmParsers`、`battlePlan`
|
||
- 关键主链如 `useStoryGeneration`、`useCombatFlow` 播放层、`GameShell` 集成链路、编辑器保存流程仍然没有直接测试
|
||
|
||
### 影响
|
||
|
||
- 代码库会持续积累“已知 warning,但先不处理”的债务
|
||
- 工程信号会逐渐失真,lint 通过不代表代码足够干净
|
||
- 大 hook 和大组件的重构依然主要依赖人工回归
|
||
|
||
### 建议
|
||
|
||
- 先把 warning 收敛到一个可控范围,再把全仓 `lint` 切成 `--max-warnings 0`
|
||
- `lint:guardrails` 不要长期靠 allowlist,应该逐步扩大到全仓
|
||
- 优先补三类测试:
|
||
- `useStoryGeneration` 的状态推进和 modal 决策
|
||
- `useCombatFlow` 播放层的关键分支
|
||
- 编辑器保存链路和覆盖数据回写
|
||
|
||
## P2:运行时渲染层仍然偏重,chunk 也还没有拆到理想粒度
|
||
|
||
### 现状
|
||
|
||
入口已经有了 route lazy load,模态框也做了一部分懒加载,但核心运行时渲染层仍然比较重。
|
||
|
||
### 证据
|
||
|
||
- `src/components/AdventurePanel.tsx:470` 导出主组件,文件总长约 `1538` 行
|
||
- `src/components/GameCanvas.tsx:472` 导出主组件,文件总长约 `1131` 行
|
||
- `src/components/GameCanvas.tsx:768` 仍然存在 `false && companions.map(...)` 的死分支
|
||
- 本次构建里 `GameCanvas` 和 `App` 仍然是最大 chunk 之一
|
||
|
||
### 影响
|
||
|
||
- 运行时页面的首屏与热区模块仍然偏重
|
||
- 渲染逻辑、场景动画逻辑、实体选中逻辑继续堆在同一层,review 和测试都偏吃力
|
||
- 清理死分支前,维护者对“哪些渲染路径是真实生效的”判断成本更高
|
||
|
||
### 建议
|
||
|
||
- `GameCanvas` 继续拆成 scene layer、entity layer、effect layer、overlay layer
|
||
- `AdventurePanel` 继续下沉 quest/stats/settings/reward 子面板
|
||
- 清理 `false &&` 与未使用辅助组件,避免假分支继续留在主路径文件中
|
||
- 结合真实 chunk 数据做一次 route 内部分包,而不是只靠入口级 lazy
|
||
|
||
## P2:TypeScript 安全基线仍然偏宽松
|
||
|
||
### 现状
|
||
|
||
当前类型拆分方向是好的,`src/types.ts` 已经退化成 barrel,真实类型开始向 `src/types/` 下沉。但 TypeScript 编译配置还比较保守,类型系统还没有真正变成强约束。
|
||
|
||
### 证据
|
||
|
||
- `tsconfig.json:12` `skipLibCheck: true`
|
||
- `tsconfig.json:16` `allowJs: true`
|
||
- 当前没有启用 `strict`
|
||
- 当前没有启用 `noUncheckedIndexedAccess`
|
||
|
||
### 影响
|
||
|
||
- 对大对象和字典访问的保护仍然偏弱
|
||
- 新模块迁移到更细类型后,收益会被宽松编译选项部分抵消
|
||
- “代码能过类型检查”并不等于边界足够安全
|
||
|
||
### 建议
|
||
|
||
- 不建议一次性全仓开严格模式
|
||
- 可以先从 `src/services/`、`src/persistence/`、`src/hooks/combat/` 这些相对纯的目录启更严格约束
|
||
- 至少先评估开启 `noUncheckedIndexedAccess` 和减少 `allowJs` 的必要性
|
||
|
||
## 建议的落地顺序
|
||
|
||
### 第一阶段:先把过渡态清干净
|
||
|
||
- 清理 `ai.ts` 的旧 transport / parser / fallback 实现
|
||
- 清理 `NpcVisualEditor`、`GameCanvas`、`PresetEditor` 等文件里的 `if (false)`、未使用 helper、废弃注释块
|
||
- 把 lint warning 数量先打下来
|
||
|
||
### 第二阶段:拆主链,不再让大 hook 继续膨胀
|
||
|
||
- 继续拆 `useStoryGeneration`
|
||
- 继续拆 `useCombatFlow`
|
||
- 优先把“领域动作”和“播放/展示编排”分开
|
||
|
||
### 第三阶段:补门禁
|
||
|
||
- 给主链补单测和少量集成 smoke
|
||
- 让全仓 lint 朝 `--max-warnings 0` 收敛
|
||
- 把 warning 从“长期存在”变成“短周期清零”
|
||
|
||
### 第四阶段:优化运行时体积
|
||
|
||
- 细化 `GameCanvas` 和 `AdventurePanel` 的模块边界
|
||
- 按实际交互热区做 chunk 继续拆分
|
||
- 用真实构建产物持续追踪是否降重
|
||
|
||
## 一句话结论
|
||
|
||
这轮仓库已经从“完全依赖大文件硬扛”进步到“基础设施开始成形”,但当前最需要做的已经不是继续加功能,而是把这轮重构收尾做完整:继续拆主链、删掉迁移残留、把 lint/test 门禁变硬、再顺手压缩运行时大模块。只要这一步补上,后续加剧情、加编辑器能力、加自定义世界都会轻很多。
|