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

112 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端工程质量(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` 用行为描述句。