112 lines
11 KiB
Markdown
112 lines
11 KiB
Markdown
# 前端工程质量(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` 用行为描述句。
|