13 KiB
工程优化审查报告(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/audits/engineering/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 个值得优先处理的工程问题:
- 运行时主链仍然过于集中,
story/combat的真实边界还没有彻底拆开 src/services/ai.ts仍处于迁移中间态,存在重复实现和旧逻辑残留- 编辑器主入口仍是大型聚合组件,迁移残留没有清干净
- 质量门禁已经有框架,但还不够“硬”,warning 和测试覆盖缺口仍然明显
- 运行时渲染层和构建体积仍偏重,重 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 kBdist/assets/App-*.js约326.89 kBdist/assets/index-*.js约197.80 kBdist/assets/index-*.css约117.37 kB
P0:运行时主链仍然过于集中,Story/Combat 边界还没有拆透
现状
虽然 App.tsx 已经明显瘦身,GameShell 也比之前更像壳层,但真正决定游戏推进的主逻辑仍然高度集中在两个大 hook 里:
src/hooks/useStoryGeneration.ts:824src/hooks/useCombatFlow.ts:382
证据
useStoryGeneration 仍然同时编排了多个本应继续拆开的子领域:
src/hooks/useStoryGeneration.ts:852接入useCharacterChatFlowsrc/hooks/useStoryGeneration.ts:1583接入useTreasureFlowsrc/hooks/useStoryGeneration.ts:1588接入useInventoryFlowsrc/hooks/useStoryGeneration.ts:1593接入useEquipmentFlowsrc/hooks/useStoryGeneration.ts:1597接入useForgeFlow- 文件总长仍有约
3240行 - 结尾返回对象同时暴露剧情推进、地图旅行、NPC 交易/送礼/招募、角色聊天、背包与锻造 UI 能力,典型位置在
src/hooks/useStoryGeneration.ts:3171-3219
useCombatFlow 也不是纯计算层,它仍然同时承担:
- 战斗前后状态推导
- 动画播放与时间推进
setGameState驱动的可视化编排- 逃跑流程与 story 响应同步
关键位置:
src/hooks/useCombatFlow.ts:382useCombatFlowsrc/hooks/useCombatFlow.ts:1195playEscapeSequenceWithStorySync
影响
- 任何一个“剧情选项新增”都很容易同时碰到 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.tssrc/services/llmParsers.tssrc/services/aiFallbacks.tssrc/services/aiTypes.ts
这说明拆层方向是对的。但 src/services/ai.ts 还没有真正变成“纯 orchestration 层”,里面仍然保留着一整套旧 transport / parse / fallback 逻辑。
证据
src/services/ai.ts:64-66已经开始导入llmClientsrc/services/ai.ts:89-95仍然保留本地resolveTimeoutMs和超时常量src/services/ai.ts:647仍然保留_requestPlainTextCompletionsrc/services/ai.ts:719仍然保留_parseJsonResponseTextsrc/services/ai.ts:739仍然保留_parseLineListContentsrc/services/ai.ts:784仍然保留_streamPlainTextCompletionsrc/services/ai.ts:885-904仍然保留一批旧的_buildOffline...helper
与之对应,新的实现已经在下面这些文件里存在:
src/services/llmClient.tssrc/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:402CharacterPresetPanelsrc/components/PresetEditor.tsx:1174SceneNpcPresetPanelsrc/components/PresetEditor.tsx:1547ScenePresetPanelsrc/components/PresetEditor.tsx:1852MonsterPresetPanelsrc/components/PresetEditor.tsx:2218PresetEditor- 文件总长仍有约
2279行
同时,文件里还留着明显的过渡态残留:
src/components/PresetEditor.tsx:227仍然保留未使用的_SectionCardsrc/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 0package.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 layerAdventurePanel继续下沉 quest/stats/settings/reward 子面板- 清理
false &&与未使用辅助组件,避免假分支继续留在主路径文件中 - 结合真实 chunk 数据做一次 route 内部分包,而不是只靠入口级 lazy
P2:TypeScript 安全基线仍然偏宽松
现状
当前类型拆分方向是好的,src/types.ts 已经退化成 barrel,真实类型开始向 src/types/ 下沉。但 TypeScript 编译配置还比较保守,类型系统还没有真正变成强约束。
证据
tsconfig.json:12skipLibCheck: truetsconfig.json:16allowJs: 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 门禁变硬、再顺手压缩运行时大模块。只要这一步补上,后续加剧情、加编辑器能力、加自定义世界都会轻很多。