11 KiB
11 KiB
前端工程质量(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
- 引入 ESLint + Prettier,全库一次到位 0 error,进入
npm run check与 CI 门禁。 - 建立组件测试基建(testing-library + jsdom),公共组件有少量样板测试;阅读器组件不在本期。
- 建立 Playwright e2e 基建,覆盖登录/书架/阅读/书签主流程与 admin 冒烟,含关键页 axe a11y 扫描;发布前手动门禁。
- 接入 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-eslintrecommended + 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-a11yclick-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后缀,vitestenvironmentMatchGlobs(或等价的项目级配置)将**/*.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 个,目的是验证基建而非追覆盖率):
src/components/ui/button.test.tsx:渲染、variant class、点击回调;src/components/ui/dialog.test.tsx:radix Dialog 在 jsdom 下开合、焦点管理(验证 polyfill 方案成立——② 的 chrome 迁移依赖 Sheet/Popover 等同类原语);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;登录态用 PlaywrightstorageState复用,避免每条用例重复登录。 - 用例(2 个 spec,不追全量):
e2e/auth-shelf.spec.ts:登录 → 书架可见 → 进入 CBZ 书 → 翻页 → 加入书签 → 退出登录;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新增独立e2ejob,仅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
npm run check绿 = tsc + eslint 全库 0 error + prettier check + vitest(9 存量逻辑测试 + 3 新组件样板)+ build;- dev 栈起后
npm run e2e绿; npm run analyze可出报告,docs/bundle-review.md已提交;docs/CHANGELOG_web.md双语条目齐全;docs/README.md/README_zh.md等价更新;- CI:frontend job 无需改动即覆盖 lint;新增 e2e job(workflow_dispatch)就绪;
- 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用行为描述句。