Files
book-comic-library/docs/superpowers/specs/2026-09-15-frontend-quality-design.md

11 KiB
Raw Permalink Blame History

前端工程质量(lint/format + 组件测试 + e2e/a11y + bundle 分析)设计 / Frontend engineering quality design

日期 2026-09-15。分支 docs/frontend-quality-spec(设计);实施分支另建(建议 feat/frontend-quality)。状态:已获用户批准(会话内确认)。

本 spec 是项目优化四个子项目中的 ④(其余:② 阅读器改版、③ 功能增强;① 后端健壮性已合入 master,见 2026-09-14-backend-hardening-design.md)。④ 与 ② 存在排序耦合:本 spec 先落地 lint/测试/e2e 基建,② 改阅读器时即有规范与回归网可用;阅读器组件测试明确留给 ②。

目标 / Goal

  1. 引入 ESLint + Prettier,全库一次到位 0 error,进入 npm run check 与 CI 门禁。
  2. 建立组件测试基建(testing-library + jsdom),公共组件有少量样板测试;阅读器组件不在本期。
  3. 建立 Playwright e2e 基建,覆盖登录/书架/阅读/书签主流程与 admin 冒烟,含关键页 axe a11y 扫描;发布前手动门禁。
  4. 接入 bundle 分析工具并产出书面审视结论;不做实际优化。

非目标见文末「范围外」。

决策记录 / Decisions

  • 五个方向(lint/format、组件测试、e2e、a11y、bundle 分析)全部纳入本期,单 spec、四批次依序落地(方案 A)。用户选定。否决:B(全部基建一次接通再集中修,中间态长、难定位)、C(拆两份 spec 后置 e2e/a11y/bundle,会削弱 ② 的回归安全网)。
  • lint 工具选 ESLint 9 flat config + Prettier(独立格式化),否决 Biome(a11y 规则覆盖弱于 jsx-a11y)与 Biome+ESLint 混合(双工具维护成本)。用户选定。
  • lint 严格度:全库 0 error 一次到位,不做 baseline 渐进。过噪规则显式降 warn 或关闭,且必须在 config 内注释理由。用户选定。
  • 组件测试深度:基建 + 少量样板(2-3 个文件,验证基建可用),阅读器组件测试留给 ② 随新 chrome 一起写。用户选定。
  • e2e 运行方式:针对 dev compose 栈(默认 http://localhost:5173),npm run e2e 为发布前手动门禁,不进 npm run check;CI 加独立 job,workflow_dispatch 触发。用户选定。否决:进 check(日常摩擦大)、mock 后端(测不到集成层、与真实后端漂移)。
  • a11y 分层:静态 jsx-a11y(批次 1 随 eslint 接通)+ e2e 关键页 axe 扫描(批次 3);不做组件测试层 axe 断言(与 e2e 扫描重叠)。用户选定。
  • bundle 分析:只接工具 + 出书面结论(docs/bundle-review.md),优化动作不在本期。用户选定。
  • 所有新依赖在 dev 容器内安装并提交 lockfile(AGENTS.md 约束);Playwright 浏览器二进制仅 dev 容器手动安装(文档化),不进 prod 镜像。

S0 批次总则 / Batches

四批次依序实施、依序提交,每批独立可回滚:

  • B1 lint/format → B2 组件测试基建 → B3 e2e + axe → B4 bundle 分析。
  • 每批验收底线:npm run check 保持绿 + 该批新增命令可跑通。
  • B1 最先,使后续批次新写的测试/e2e 代码天然符合 lint/format 规范。
  • changelog:纯 frontend/ 改动记 docs/CHANGELOG_web.md(双语同条、条目间空行、新版本在上);docs/bundle-review.md 与 README 改动随 B4 同批,不重复记入 docs/CHANGELOG.md。

S1 批次 1:ESLint + Prettier / Lint & format

  • 依赖(dev):eslint、typescript-eslint、eslint-plugin-react-hooks、eslint-plugin-jsx-a11y、eslint-config-prettier、prettier(prettier-plugin-tailwindcss 可选,接入与否在实施时以噪音程度定,接则 class 自动排序)。
  • 配置:frontend/eslint.config.js(flat config),基线 = typescript-eslint recommended + react-hooks recommended + jsx-a11y recommended + eslint-config-prettier 收尾关闭格式冲突规则。按目录 override:e2e/(B3 产物)用 node 环境 globals、放宽 e2e 惯例规则;test/、*.test.tsx 允许测试专用 globals。
  • Prettier:frontend/.prettierrc(对齐现有代码风格:2 空格、单引号——以实施时对存量代码 diff 最小化为准)+ .prettierignore(dist/、node_modules/、package-lock.json、dist-stats/)。
  • scripts(frontend/package.json):lint(eslint .)、lint:fix、format(prettier --write .)、format:check;check 改为 tsc --noEmit && eslint . && prettier --check . && vitest run && vite build。
  • 存量修复:全库 eslint error 清零;prettier 首次全量 format 单独成一个 commit(纯格式、无逻辑改动),便于 review 与回滚。
  • 降 warn/关闭的规则须在 eslint.config.js 内逐条注释理由(例如 jsx-a11y click-events-have-key-events 对阅读器中央点击区可先 warn,② 改版时收严)。
  • CI:frontend job 已跑 npm run check,lint 自动覆盖,无需改 workflow。

验收:npm run check 绿;eslint . 0 error;prettier --check . 0 diff。

S2 批次 2:组件测试基建 / Component testing

  • 依赖(dev):jsdom、@testing-library/react、@testing-library/user-event、@testing-library/jest-dom。
  • 环境分层:存量 frontend/test/*.test.ts 纯逻辑测试保持 node 环境不动;组件测试用 *.test.tsx 后缀,vitest environmentMatchGlobs(或等价的项目级配置)将 **/*.test.tsx 路由到 jsdom;vitest run 一次全跑,不加新 script。
  • setup:frontend/test/setup.ts —— 注册 jest-dom matchers、自动 cleanup、补 jsdom 缺口 polyfill(ResizeObserver、Element.scrollIntoView、pointer events),radix 组件在 jsdom 下的已知坑一次配好。
  • 放置约定:组件测试与组件同目录 colocate(如 src/components/ui/button.test.tsx);纯逻辑测试维持 frontend/test/。约定写入 B1 的 eslint override 与本 spec 附录(S6)。
  • 样板测试(3 个,目的是验证基建而非追覆盖率):
    1. src/components/ui/button.test.tsx:渲染、variant class、点击回调;
    2. src/components/ui/dialog.test.tsx:radix Dialog 在 jsdom 下开合、焦点管理(验证 polyfill 方案成立——② 的 chrome 迁移依赖 Sheet/Popover 等同类原语);
    3. src/components/theme-toggle.test.tsx:真实业务组件,验证 localStorage/主题上下文可测。
  • 阅读器组件(src/readers/{Cbz,Epub,Pdf,Text}Reader.tsx)本期不写组件测试;spec 显式约定 ② 改版时随新 chrome 补齐并作为行为回归网。

验收:npm run check 绿;3 个样板测试通过;存量 9 个逻辑测试不受影响。

S3 批次 3:e2e + a11y 运行时扫描 / e2e & axe

  • 依赖(dev):@playwright/test、@axe-core/playwright;浏览器仅装 chromium(dev 容器内 npx playwright install --with-deps chromium,命令写入 README 与本 spec)。
  • 结构:frontend/e2e/*.spec.ts + frontend/playwright.config.ts;baseURL 取环境变量 E2E_BASE_URL,默认 http://localhost:5173(dev compose 栈 web 端口)。不由 Playwright 管理 server 生命周期——前提是手动起好 dev 栈(deploy/docker-compose.dev.yml),连不上直接失败并给出提示。
  • 测试数据:全部走 HTTP API 准备(注册/登录、上传 e2e/fixtures/ 下的小 CBZ 文件),不直接碰 PG/Redis;登录态用 Playwright storageState 复用,避免每条用例重复登录。
  • 用例(2 个 spec,不追全量):
    1. e2e/auth-shelf.spec.ts:登录 → 书架可见 → 进入 CBZ 书 → 翻页 → 加入书签 → 退出登录;
    2. e2e/admin-smoke.spec.ts:admin 登录 → 建库 → 触发扫描 → 扫描入口/状态可见(冒烟级;扫描状态 API 属 ③,此处只验证现有行为)。
  • axe 扫描:在关键页面(登录页、书架页、阅读器 chrome 展开态)插入 @axe-core/playwright 扫描步骤。门禁分级:
    • critical/serious 违规 = 测试红;moderate/minor 仅输出报告不阻断;
    • 存量 critical/serious 违规本批内修掉(多为属性级小修,记 CHANGELOG_web.md);确属将被 ② 重写的旧阅读器 chrome 的问题,允许在测试内逐条显式 allowlist 豁免,每条注释指向 ②。
  • scripts:e2e(playwright test)、e2e:ui(playwright test --ui,开发调试用)。不进 npm run check。
  • CI:ci.yml 新增独立 e2e job,仅 workflow_dispatch 触发,job 内自起 PG/Redis/api/web(service 容器 + build),runner 未就绪期间形同文档,就绪后零改动可用。
  • eslint:e2e/ 目录 override(node globals、测试文件规则)。

验收:dev 栈起后 npm run e2e 绿(2 spec + 关键页 axe 无未豁免的 critical/serious)。

S4 批次 4:bundle 分析 / Bundle analysis

  • 依赖(dev):rollup-plugin-visualizer;接入方式为 vite.config.ts 内按条件启用(如 ANALYZE=1 或 --mode analyze),产物输出到 frontend/dist-stats/(加入 .gitignore 与 .prettierignore,不进 dist 发布产物)。
  • script:analyze。
  • 审视结论文档:docs/bundle-review.md(英中双语,随批 commit)。内容:各大依赖(pdfjs-dist、epubjs、marked、dompurify、react-query、radix-ui、lucide-react 等)的体积占比、当前是否已懒加载/分包、可优化点清单;每个优化项标注建议归属(② 或后续 spec),本期不动代码。
  • README:docs/README.md 与 docs/README_zh.md 同批补充新增命令(lint/format/analyze/e2e/e2e:ui)与本地门禁清单更新,保持双语内容等价(AGENTS.md 约束)。

验收:npm run analyze 可出报告;docs/bundle-review.md 提交;README 双语等价。

S5 整体验收 / Definition of Done

  1. npm run check 绿 = tsc + eslint 全库 0 error + prettier check + vitest(9 存量逻辑测试 + 3 新组件样板)+ build;
  2. dev 栈起后 npm run e2e 绿;
  3. npm run analyze 可出报告,docs/bundle-review.md 已提交;
  4. docs/CHANGELOG_web.md 双语条目齐全;docs/README.md / README_zh.md 等价更新;
  5. CI:frontend job 无需改动即覆盖 lint;新增 e2e job(workflow_dispatch)就绪;
  6. lockfile 已提交;prod 镜像不新增任何本期依赖。

S6 范围外 / Out of scope

  • 阅读器组件的任何测试与改动(→ ② 阅读器改版);
  • bundle 体积的实际优化动作(仅出结论清单);
  • moderate/minor 级 a11y 修复(仅输出报告);组件测试层 axe 断言;
  • Playwright 浏览器进 prod 镜像;e2e 进 npm run check;
  • 后端任何改动(含扫描状态 API → ③);
  • components/ui.ts(deprecated)与 components/icons.tsx 的清理(→ ②,本期 lint 仅按现状规则放行或豁免,不做删除)。

附录:测试放置与命名约定 / Appendix: test conventions

  • 纯逻辑测试:frontend/test/<module>.test.ts(现状不变,node 环境);
  • 组件测试:与被测组件同目录 <name>.test.tsx(jsdom 环境);
  • e2e:frontend/e2e/<flow>.spec.ts,fixture 放 frontend/e2e/fixtures/;
  • 测试命名:describe 用被测单元名,it 用行为描述句。