跳转至

维护性重构 — frontend/src/pages 模块化

状态: 计划(尚未执行) · 起草日期: 2026-06-30 · 类型: 纯机械性重构,无行为变更

为什么

frontend/src/pages/ 中有些文件已增长到相当大的规模,任何编辑都会把整个文件重新加载到上下文中,并带来无关破坏的风险。编辑某个 Settings 面板时,应该只需改动一个约 150 行的文件,而不是一个 1969 行的文件。这既能提高可维护性,也能降低每次编辑的 token 成本。

这种修复不是一种新架构——components/settings/ 已经证明了目标模式(13 个已抽取的 *Panel.jsx,每个都自带同目录的 .css/.test.jsx,外加一个共享的 primitives/ 文件夹)。本次重构完成了一项停滞的迁移,然后将其固化,避免文件悄悄膨胀回原状。

当前状态(2026-06-30 实测)

文件 行数 备注
pages/Settings.jsx 1969 仍然内联:ModelStoreTab(约 790 行)、Settings 编排器(约 600 行)、GeneralTab、EnginesTab、HotkeyTab、CredentialsTab,以及 Row/fmtBytes/orgColor 辅助函数
pages/DubTab.jsx 1592 一个巨型组件 + 内联的 DubFailureNotice、DubPipelineStepper、PrepOverlay、TranscribeOverlay、FooterBtn
pages/CloneDesignTab.jsx 837
pages/VoiceGallery.jsx 768
pages/VoiceProfile.jsx 515
pages/AudiobookTab.jsx 402 在阶段 3 清理后处于目标范围内
其他所有文件 <340 在目标范围内

已抽取的部分不要动(参考模式):components/settings/*Panel.jsx、components/settings/primitives/。

黄金标准(提议)

  1. 大小上限: 每个 .jsx/.css 软上限 300 行,硬上限 500 行。超过 500 行必须拆分。
  2. 页面应是薄编排器: 页面 = 布局 + 路由 + 状态接线,并组合功能组件。不允许任何内联子组件超过约 50 行。
  3. 每个文件一个组件,同目录放置 Foo.jsx + Foo.css + Foo.test.jsx,并按页面的功能文件夹分组:
  4. components/settings/(已存在)
  5. components/dub/(新建)
  6. components/clone/(新建)
  7. components/gallery/(新建)
  8. 共享部分 → primitives/,放在功能文件夹内(settings 已经这样做了)。
  9. 用 ESLint 的 max-lines 来强制——先仅告警(warn-only),这样它永远不会破坏 CI(遵循“保持主分支绿色”的规则),待积压清理完成后升级为错误。

阶段(每个阶段 = 一个可合并、CI 通过的 PR)

阶段 0 — 规范 + 护栏

  • 将大小/结构规则添加到 CONTRIBUTING.md(反正文档同步规则也要求这样做)。
  • 添加 ESLint 配置 max-lines: ['warn', { max: 500, skipBlankLines: true, skipComments: true }]。
  • 不移动任何代码。尽可能小的 PR;建立约定。

阶段 1 — Settings.jsx(最大收益:1969 → 约 300 行)

抽取到 components/settings/ 中,沿用现有面板命名方式:

抽取内容 当前行数(约) 新文件
ModelStoreTab(+ Row、fmtBytes、orgColor、MODEL_ROLE_*) 229–1021 ModelStoreTab.jsx(可能进一步拆分:表格 vs 矩阵 vs 行)
GeneralTab 80–201 GeneralTab.jsx
EnginesTab 1022–1072 EnginesTab.jsx
HotkeyTab(+ CREDENTIAL_FIELDS、keyEventToAccelerator) 1693–1870 HotkeyTab.jsx
CredentialsTab 1871–1969 CredentialsTab.jsx

Settings.jsx 仅保留:imports、TAB_DEFS/LOG_SOURCE_DEFS、Settings 默认导出(标签页路由 + 共享状态),以及 askConfirm。

阶段 2 — DubTab.jsx(1592 → 编排器 + components/dub/)

将 DubFailureNotice、DubPipelineStepper、PrepOverlay、TranscribeOverlay、FooterBtn 以及较大的渲染子区块抽取到 components/dub/。DubTab.jsx 保留流水线状态机 + 组合逻辑。

阶段 3 — CloneDesignTab、VoiceGallery、VoiceProfile、AudiobookTab

对它们执行同样的处理,放入 components/clone/ 和 components/gallery/。规模更小,风险更低。

遵循的约束

  • 无行为变更 —— 纯移动;可通过“应用渲染结果一致 + 现有测试通过”来验证 diff。每个有测试的面板都保留其测试。
  • 保持主分支绿色 —— ESLint 规则为仅告警;每个阶段都独立保持 CI 绿色。
  • 文档同步 —— 阶段 0 将 CONTRIBUTING.md 的改动与规则放在同一个 PR 中。
  • 无版本化影响 —— 仅前端重构;不提升 package.json 版本,不更改 lockfile/依赖,不触及 Docker/Tauri/Python。

每个阶段的验证

  1. bun run build(或项目的 typecheck/lint)通过。
  2. 现有的 components/settings/*.test.jsx(以及任何新的同目录测试)通过。
  3. 手动冒烟测试:打开 Settings → 每个标签页都能渲染;打开 Dub → 流水线能渲染。
  4. git diff --stat 显示只有移动(行数在文件之间变化,逻辑变更净约为 0)。

明确不在范围内

  • 不重新设计 Settings UI 本身(“不整齐”的外观)——那是另一项视觉打磨任务;本次重构只调整代码结构。如果你希望一并处理,请标记出来。
  • 不进行 .jsx → .tsx 转换(页面目前是 JS;TS 迁移是另一个单独的决策)。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio