@captain_z/zsk-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +263 -0
  2. package/bundles.yaml +104 -0
  3. package/design-handoff/.gitkeep +0 -0
  4. package/design-handoff/figma-to-code/SKILL.md +265 -0
  5. package/design-handoff/ue-mcp/SKILL.md +184 -0
  6. package/frontend/.gitkeep +0 -0
  7. package/frontend/a11y-web/SKILL.md +169 -0
  8. package/frontend/api-contract-ts/SKILL.md +275 -0
  9. package/frontend/css-bem/SKILL.md +268 -0
  10. package/frontend/design-frontend/SKILL.md +163 -0
  11. package/frontend/dor-dod-frontend/SKILL.md +114 -0
  12. package/frontend/feature-tasks-frontend/SKILL.md +246 -0
  13. package/frontend/i18n/SKILL.md +296 -0
  14. package/frontend/nfr-web/SKILL.md +258 -0
  15. package/frontend/performance-web/SKILL.md +299 -0
  16. package/frontend/react-components/SKILL.md +211 -0
  17. package/frontend/react-naming/SKILL.md +224 -0
  18. package/frontend/review-frontend/SKILL.md +126 -0
  19. package/frontend/security-web/SKILL.md +286 -0
  20. package/frontend/spec-frontend/SKILL.md +141 -0
  21. package/frontend/testing-web/SKILL.md +252 -0
  22. package/frontend/typescript/SKILL.md +219 -0
  23. package/meta/.gitkeep +0 -0
  24. package/meta/philosophy/SKILL.md +221 -0
  25. package/package.json +24 -0
  26. package/quality/.gitkeep +0 -0
  27. package/quality/a11y-principles/SKILL.md +155 -0
  28. package/quality/code-hygiene/SKILL.md +126 -0
  29. package/quality/release/SKILL.md +209 -0
  30. package/quality/security-owasp/SKILL.md +157 -0
  31. package/quality/testing-pyramid/SKILL.md +173 -0
  32. package/sdlc/.gitkeep +0 -0
  33. package/sdlc/archive/SKILL.md +267 -0
  34. package/sdlc/bugfix/SKILL.md +181 -0
  35. package/sdlc/bugfix-tasks/SKILL.md +232 -0
  36. package/sdlc/coding/SKILL.md +177 -0
  37. package/sdlc/design/SKILL.md +299 -0
  38. package/sdlc/dor-dod/SKILL.md +120 -0
  39. package/sdlc/feature/SKILL.md +162 -0
  40. package/sdlc/proposal/SKILL.md +271 -0
  41. package/sdlc/refactor/SKILL.md +220 -0
  42. package/sdlc/refactor-tasks/SKILL.md +265 -0
  43. package/sdlc/reviewing/SKILL.md +197 -0
  44. package/sdlc/spec/SKILL.md +235 -0
  45. package/sdlc/task/SKILL.md +116 -0
  46. package/sdlc/task-evidence/SKILL.md +178 -0
  47. package/sdlc/task-structure/SKILL.md +153 -0
  48. package/sdlc/task-tracking/SKILL.md +192 -0
  49. package/sdlc/verify/SKILL.md +181 -0
  50. package/system/.gitkeep +0 -0
  51. package/system/adr/SKILL.md +169 -0
  52. package/system/architecture/SKILL.md +182 -0
  53. package/system/glossary/SKILL.md +141 -0
  54. package/system/nfr-baseline/SKILL.md +156 -0
  55. package/system/project-constraints-template/SKILL.md +241 -0
@@ -0,0 +1,258 @@
1
+ ---
2
+ name: zsk:nfr-web
3
+ description: Web-side NFR thresholds for the 7-category framework — Core Web
4
+ Vitals (LCP/INP/CLS/TTFB/TTI targets), WCAG 2.1 AA itemized rules, Web
5
+ security NFR items, i18n language list + baseline + entry + RTL,
6
+ browser/OS/device compatibility matrix, responsive breakpoints, observability
7
+ (web-vitals + error reporting), reliability (ErrorBoundary + timeout + retry).
8
+ Inherits system/nfr-baseline; modules declare deviations only.
9
+ category: resource
10
+ domain: frontend
11
+ tier: optional
12
+ related:
13
+ - ../performance-web/SKILL.md
14
+ - ../a11y-web/SKILL.md
15
+ - ../security-web/SKILL.md
16
+ - ../i18n/SKILL.md
17
+ - ../testing-web/SKILL.md
18
+ - ../../system/nfr-baseline/SKILL.md
19
+ - ../../quality/a11y-principles/SKILL.md
20
+ - ../../quality/security-owasp/SKILL.md
21
+ triggers:
22
+ - Web NFR
23
+ - CWV thresholds
24
+ - WCAG AA rules
25
+ - browser matrix
26
+ - responsive breakpoints
27
+ - web-vitals reporting
28
+ - ErrorBoundary placement
29
+ - NFR deviation declaration
30
+ ---
31
+
32
+ # NFR Web · Web 非功能需求具体阈值
33
+
34
+ > **定位**:[`system/nfr-baseline`](../../system/nfr-baseline/SKILL.md) 7 类框架的 **Web 具体化**
35
+ > **继承规则**:模块 `spec.md` NFR 章节继承本文件,**只声明偏离**
36
+ > **Web 实现细节**:性能 → [`performance-web`](../performance-web/SKILL.md) · a11y → [`a11y-web`](../a11y-web/SKILL.md) · 安全 → [`security-web`](../security-web/SKILL.md) · i18n → [`i18n`](../i18n/SKILL.md) · 测试 → [`testing-web`](../testing-web/SKILL.md)
37
+
38
+ ## 1. 性能(NFR-1)
39
+
40
+ ### Core Web Vitals 目标(线上 p75)
41
+
42
+ | 指标 | Good | 需改进 | 差 |
43
+ | --- | --- | --- | --- |
44
+ | **LCP** | ≤ 2.5s | ≤ 4.0s | > 4.0s |
45
+ | **INP** | ≤ 200ms | ≤ 500ms | > 500ms |
46
+ | **CLS** | ≤ 0.1 | ≤ 0.25 | > 0.25 |
47
+ | **TTFB** | ≤ 800ms | ≤ 1.8s | > 1.8s |
48
+ | **TTI** | ≤ 3.5s | ≤ 7.3s | > 7.3s |
49
+ | **TBT**(实验室) | ≤ 200ms | ≤ 600ms | > 600ms |
50
+
51
+ ### Bundle 预算
52
+
53
+ | 维度 | 基线 |
54
+ | --- | --- |
55
+ | 单 chunk(gzip) | ≤ 500KB |
56
+ | 首屏 JS 总(gzip) | ≤ 1MB |
57
+ | 首屏 CSS 总(gzip) | ≤ 100KB |
58
+ | PR 新增首屏 JS | ≤ 10% 增量(`size-limit` 门禁) |
59
+
60
+ ### 网络并发
61
+
62
+ | 维度 | 基线 |
63
+ | --- | --- |
64
+ | 首屏并发请求 | ≤ 6 |
65
+ | 同参数请求去重率 | 100% |
66
+ | 超过预算 → `design.md` 记 ADR | — |
67
+
68
+ ### 运行时性能
69
+
70
+ | 维度 | 基线 |
71
+ | --- | --- |
72
+ | 列表渲染一帧内 | ≤ 16ms(60fps)|
73
+ | 节点数 ≥ 200 必须虚拟化 | — |
74
+ | 输入防抖 | ≥ 300ms |
75
+
76
+ 详见 [`performance-web`](../performance-web/SKILL.md)。
77
+
78
+ ## 2. 可访问性(NFR-2)
79
+
80
+ ### 基线:WCAG 2.1 Level AA
81
+
82
+ (原则层见 [`quality/a11y-principles`](../../quality/a11y-principles/SKILL.md);此处列 Web 侧可被工具直接校验的条目)
83
+
84
+ | WCAG 条目 | 工具校验 |
85
+ | --- | --- |
86
+ | 1.1.1 Non-text Content(alt) | `jsx-a11y/alt-text` |
87
+ | 1.3.1 Info and Relationships(语义 HTML)| `jsx-a11y/*` |
88
+ | 1.4.3 Contrast (Minimum)(4.5:1 / 3:1) | axe-core |
89
+ | 1.4.4 Resize text(200%) | 手工 |
90
+ | 2.1.1 Keyboard | `jsx-a11y/click-events-have-key-events` + 手工 |
91
+ | 2.1.2 No Keyboard Trap | 手工(焦点测试)|
92
+ | 2.4.3 Focus Order | 手工 |
93
+ | 2.4.7 Focus Visible | CSS `:focus-visible` |
94
+ | 3.3.2 Labels or Instructions | `jsx-a11y/label-has-associated-control` |
95
+ | 4.1.2 Name/Role/Value | axe-core |
96
+ | 4.1.3 Status Messages | `aria-live` 用法 |
97
+
98
+ ### 工具链硬门禁
99
+
100
+ - `eslint-plugin-jsx-a11y` 零 error
101
+ - `axe-core` / `@axe-core/react` 零 `critical` / `serious` violation
102
+ - `jest-axe` 每组件 ≥ 1 条断言
103
+ - `@axe-core/playwright` 关键页面扫描
104
+
105
+ ### 键盘操作矩阵
106
+
107
+ 见 [`quality/a11y-principles`](../../quality/a11y-principles/SKILL.md) 键盘矩阵;组件特有键位在模块 `spec.md` UE 状态矩阵声明。
108
+
109
+ ## 3. 安全(NFR-3)
110
+
111
+ ### Web 侧必达红线
112
+
113
+ | 项 | 基线 |
114
+ | --- | --- |
115
+ | `dangerouslySetInnerHTML` | 无新增(或 ADR + DOMPurify) |
116
+ | 敏感字段进 Storage | 绝对禁止 |
117
+ | CSRF Token 注入修改类请求 | 100% |
118
+ | 生产 `console.log` | 0 |
119
+ | 日志 / 上报敏感字段 | 0(经脱敏) |
120
+ | URL / 跳转协议 | 白名单校验 |
121
+ | 第三方 CDN 脚本 | 带 SRI |
122
+ | `postMessage` 源校验 | 100% |
123
+ | `npm audit` `high` / `critical` | 0 |
124
+ | 环境变量敏感值暴露前端 | 0 |
125
+
126
+ 详见 [`security-web`](../security-web/SKILL.md);跨栈原则见 [`quality/security-owasp`](../../quality/security-owasp/SKILL.md)。
127
+
128
+ ### CSP / 响应 header(需后端配合)
129
+
130
+ | header | 期望 |
131
+ | --- | --- |
132
+ | Content-Security-Policy | 严格白名单(见 `security-web` 第 5 节) |
133
+ | Strict-Transport-Security | `max-age=31536000; includeSubDomains` |
134
+ | X-Content-Type-Options | `nosniff` |
135
+ | X-Frame-Options | `DENY`(或 CSP `frame-ancestors 'none'`) |
136
+ | Referrer-Policy | `strict-origin-when-cross-origin` |
137
+ | Permissions-Policy | 按需收敛 |
138
+
139
+ ## 4. 国际化(NFR-4)
140
+
141
+ ### 语言清单
142
+
143
+ | 项 | 基线 |
144
+ | --- | --- |
145
+ | 支持语言 | `{{config.i18n.languages}}`(通常 `zh_CN` / `zh_TW` / `en_US`) |
146
+ | 基线语言 | `{{config.i18n.languages}}` 的第一个(通常 `zh_CN`) |
147
+ | fallback 必须 = 基线 | 100% |
148
+ | 同 commit 三语同步 | 硬门禁 |
149
+ | key 命名 | `{{config.i18n.namespace}}.<module>.<leaf_key>` |
150
+ | 复数 / 选择 | ICU MessageFormat |
151
+ | 日期 / 数字 / 货币 | `Intl.*`(不硬编码) |
152
+
153
+ ### RTL
154
+
155
+ - 当前模块**不支持 RTL**(语言清单不含 `ar` / `he`)→ 在 `spec.md` NFR 声明
156
+ - 若启用:逻辑属性(`margin-inline-*`)+ 图标方向适配 + `dir="rtl"` 注入
157
+
158
+ 详见 [`i18n`](../i18n/SKILL.md)。
159
+
160
+ ## 5. 兼容性(NFR-5)
161
+
162
+ **浏览器矩阵**(默认基线):Chrome / Edge / Safari / Firefox 桌面端最近 2 个大版本;iOS Safari / Android Chrome 最近 2 个大版本;**IE 11 不支持**。在 `package.json` 配 `browserslist` 对齐(`last 2 <browser> versions` + `not dead`)。
163
+
164
+ **响应式断点**(引自 token,详见 [`css-bem`](../css-bem/SKILL.md) 第 7 节):
165
+
166
+ | 断点 | ≤ px | 说明 |
167
+ | --- | ---: | --- |
168
+ | xs | 480 | 小手机 |
169
+ | sm | 768 | 大手机 / 小平板 |
170
+ | md | 1024 | 平板横 / 小笔记本 |
171
+ | lg | 1280 | 笔记本 |
172
+ | xl | 1536 | 桌面 / 大屏 |
173
+
174
+ **策略**:Mobile-first(`min-width` 向上叠加)。
175
+
176
+ **输入设备**:鼠标 / 键盘 / 触摸(移动端断点内)必须支持;手写笔不在基线。
177
+
178
+ **离线 / 弱网**:默认无离线能力;需要时 `spec.md` 声明 + Service Worker / 缓存策略。弱网(慢 3G)首屏可用(骨架屏 / 懒加载)。
179
+
180
+ ## 6. 可观测性(NFR-6)
181
+
182
+ **Web Vitals 上报**:用 `web-vitals` 库订阅 `onLCP` / `onINP` / `onCLS` / `onTTFB`,回调里 `navigator.sendBeacon('/api/metrics', payload)` 上报(PII 脱敏)。
183
+
184
+ **错误上报**:
185
+
186
+ | 维度 | 基线 |
187
+ | --- | --- |
188
+ | JS 错误 | 100% 捕获(`window.onerror` + `unhandledrejection`) |
189
+ | 上报平台 | `{{config.stack.error_reporter}}`(Sentry / 自研) |
190
+ | 上报前脱敏 | 100%(含 breadcrumbs) |
191
+ | Source Map | 私有上传(不公网) |
192
+ | 采样率 | 生产 100% error / performance 采样 |
193
+
194
+ **埋点**:统一 SDK(`{{config.stack.tracking}}`);PII hash / 脱敏;GDPR 拒绝时不上报。**Trace**(可选):入口注入 `traceparent`,前后端日志关联。
195
+
196
+ ## 7. 可靠性(NFR-7)
197
+
198
+ **ErrorBoundary 放置**:App 根(兜底降级页)+ 路由级(单页崩溃不污染全站)+ 关键组件(图表 / 富文本等第三方重依赖)。
199
+
200
+ ```tsx
201
+ <ErrorBoundary fallback={<GlobalErrorFallback />}>
202
+ <Router>
203
+ <Route path="/user" element={
204
+ <ErrorBoundary fallback={<RouteErrorFallback />}><UserPage /></ErrorBoundary>
205
+ }/>
206
+ </Router>
207
+ </ErrorBoundary>
208
+ ```
209
+
210
+ **请求层基线**:默认超时 10s;幂等请求(GET / HEAD)最多 2 次指数退避重试,POST 等不重试;`AbortController` 在 effect cleanup 调用;loading / empty / error 三态 UI 必须齐。
211
+
212
+ **降级**:第三方不可用 → 本地 fallback;图表加载失败 → data table 替代;字体失败 → `font-display: swap`。
213
+
214
+ ## 8. 模块 `spec.md` NFR 章节模板
215
+
216
+ 继承本基线,只列**偏离项**(未偏离项不重复)。完整模板见 [`dor-dod-frontend`](../dor-dod-frontend/SKILL.md) DoR 定义。
217
+
218
+ ```md
219
+ ## NFR · 非功能需求
220
+
221
+ 本模块继承 `frontend/nfr-web.md` 基线。偏离项:
222
+
223
+ - NFR-1 性能:INP 收紧为 100ms(业务对输入响应敏感);首屏 JS 收紧 800KB
224
+ - NFR-3 安全:富文本编辑器用 `dangerouslySetInnerHTML`(ADR-015 + DOMPurify)
225
+ - NFR-5 兼容性:新增 IE Edge Legacy(内部系统;ADR-016)
226
+ - NFR-7 可靠性:`<UserTable>` 组件级 ErrorBoundary(依赖 AG Grid)
227
+ ```
228
+
229
+ ## 9. 证据要求
230
+
231
+ | NFR 类别 | 证据 |
232
+ | --- | --- |
233
+ | 性能 | Lighthouse 报告 + `size-limit` CI log + 线上 p75 截图 |
234
+ | a11y | axe-core 扫描 0 critical/serious + 键盘回归记录 |
235
+ | 安全 | `npm audit` 报告 + PR diff 搜索关键红线 |
236
+ | i18n | 语言文件 diff + 三语截图 |
237
+ | 兼容 | BrowserStack / Playwright 多浏览器报告 |
238
+ | 可观测 | Sentry / 埋点平台 dashboard 截图 |
239
+ | 可靠性 | 错误注入测试 + ErrorBoundary 截图 |
240
+
241
+ ## 反模式(禁止)
242
+
243
+ - ❌ "性能 / a11y / 安全后期再补"(必须随 PR 交付)
244
+ - ❌ NFR 偏离不声明(静默降低基线)
245
+ - ❌ 只过了 Lighthouse 实验室却无线上 RUM 数据
246
+ - ❌ 浏览器矩阵在 PR 中被悄悄缩减
247
+ - ❌ 断点散落各模块(必须引自 token)
248
+
249
+ ## 质量门禁(聚合)
250
+
251
+ - [ ] Web Vitals p75 达标(NFR-1)
252
+ - [ ] `eslint-plugin-jsx-a11y` + axe 零 critical/serious(NFR-2)
253
+ - [ ] 安全红线全绿 + `npm audit` 无 high(NFR-3)
254
+ - [ ] 三语同 commit 同步(NFR-4)
255
+ - [ ] 浏览器矩阵 CI 通过(NFR-5)
256
+ - [ ] Web Vitals + 错误上报就位(NFR-6)
257
+ - [ ] ErrorBoundary + 超时 / 竞态 / 失败 UI 齐全(NFR-7)
258
+ - [ ] `spec.md` NFR 偏离清单完整 + ADR 链接
@@ -0,0 +1,299 @@
1
+ ---
2
+ name: zsk:performance-web
3
+ description: React / Web frontend performance for Core Web Vitals — CWV
4
+ thresholds (LCP/INP/CLS/TTFB/TTI), React memoization discipline (when to memo
5
+ vs not), virtualization for lists ≥ 200, lazy loading via React.lazy +
6
+ Suspense + IntersectionObserver, bundle splitting + tree-shaking, image srcset
7
+ + loading="lazy", debounce/throttle, re-render diagnosis with React DevTools
8
+ Profiler. Implementation layer; budget framework in system/nfr-baseline +
9
+ frontend/nfr-web.
10
+ category: standard
11
+ domain: frontend
12
+ tier: optional
13
+ related:
14
+ - ../react-components/SKILL.md
15
+ - ../nfr-web/SKILL.md
16
+ - ../../system/nfr-baseline/SKILL.md
17
+ triggers:
18
+ - Core Web Vitals
19
+ - LCP INP CLS
20
+ - React memo
21
+ - virtual scrolling
22
+ - React.lazy Suspense
23
+ - code splitting
24
+ - bundle size
25
+ - image lazy loading
26
+ - debounce throttle
27
+ - re-render diagnosis
28
+ ---
29
+
30
+ # Performance Web · Web 性能实现
31
+
32
+ > **范围**:React + Web 的**性能实现手法** — CWV 预算 / memo 纪律 / 虚拟化 / 懒加载 / Bundle / 图片 / 防抖节流 / 诊断
33
+ > **预算框架**:见 [`system/nfr-baseline`](../../system/nfr-baseline/SKILL.md) 第 1 节
34
+ > **具体阈值**:见 [`nfr-web`](../nfr-web/SKILL.md)
35
+ > **不含**:跨栈性能原则(RAIL、服务端性能)
36
+
37
+ ## 1. Core Web Vitals 阈值
38
+
39
+ | 指标 | 含义 | 目标(Good) | 警戒(Needs Improvement) | 差 |
40
+ | --- | --- | --- | --- | --- |
41
+ | **LCP** | 最大内容绘制 | ≤ 2.5s | ≤ 4.0s | > 4.0s |
42
+ | **INP** | 交互→下次绘制(替代 FID) | ≤ 200ms | ≤ 500ms | > 500ms |
43
+ | **CLS** | 累积布局偏移 | ≤ 0.1 | ≤ 0.25 | > 0.25 |
44
+ | **TTFB** | 首字节时间 | ≤ 800ms | ≤ 1.8s | > 1.8s |
45
+ | **TTI** | 可交互时间 | ≤ 3.5s | ≤ 7.3s | > 7.3s |
46
+ | **TBT** | 总阻塞时间 | ≤ 200ms | ≤ 600ms | > 600ms |
47
+
48
+ **监测**:`web-vitals` 库上报 → 观测平台(见 `{{config.stack.error_reporter}}`)。
49
+
50
+ ## 2. 性能预算(模块级)
51
+
52
+ | 维度 | 基线 | 模块可收紧 |
53
+ | --- | --- | --- |
54
+ | 首屏时间(LCP) | ≤ 2.5s | 视功能 |
55
+ | 交互响应(INP) | ≤ 200ms | 需 ≤ 100ms 写入 design.md |
56
+ | 单 chunk 体积(gzip) | ≤ 500KB | — |
57
+ | 首屏 JS 总体积(gzip) | ≤ 1MB | — |
58
+ | 同屏并发请求 | ≤ 6 | 同参数 100% 去重 |
59
+ | 单次列表渲染 | ≤ 16ms | 一帧内 |
60
+
61
+ 超标必须在 `design.md` 记 ADR + 缓解方案。
62
+
63
+ ## 3. React memoization 纪律
64
+
65
+ **判定表**:
66
+
67
+ | 场景 | 用 | 理由 |
68
+ | --- | --- | --- |
69
+ | 子组件接收稳定引用才能避免重渲染 | `memo` + `useCallback` | 否则每次父渲染子也重渲染 |
70
+ | 计算成本 > 渲染成本 | `useMemo` | 否则每次计算浪费 |
71
+ | 依赖基本不变、内容简单 / 叶子节点 / 函数只用一次 | **不用** | memo 本身有开销 |
72
+
73
+ **常见误用**:
74
+
75
+ ```tsx
76
+ // ❌ memo 但 prop 每次都是新引用(memo 无效)
77
+ <Child onChange={(v) => setValue(v)} />
78
+ // ✅ 稳定引用
79
+ const handleChange = useCallback((v) => setValue(v), []);
80
+ <Child onChange={handleChange} />
81
+
82
+ // ❌ useMemo 包简单值 / 每个变量都 useCallback
83
+ ```
84
+
85
+ **诊断**:React DevTools Profiler "Highlight updates" 观察重渲染;DEV 环境启用 `why-did-you-render`。
86
+
87
+ ## 4. 列表虚拟化
88
+
89
+ ### 硬规则
90
+
91
+ **节点数 ≥ 200 必须虚拟化**(或 ADR 豁免)。
92
+
93
+ ### 选型
94
+
95
+ | 场景 | 库 |
96
+ | --- | --- |
97
+ | 定高列表 | `react-window` (`FixedSizeList`) |
98
+ | 动态高度列表 | `react-virtuoso` 或 `react-window` + `CellMeasurer` |
99
+ | 表格 | `@tanstack/react-virtual` |
100
+ | 树(可展开) | `react-arborist` / 自研 flatten + `react-window` |
101
+
102
+ ### 最小例子
103
+
104
+ ```tsx
105
+ import { FixedSizeList } from 'react-window';
106
+
107
+ <FixedSizeList height={600} itemCount={items.length} itemSize={48} width="100%">
108
+ {({ index, style }) => (
109
+ <div style={style}>{items[index].name}</div>
110
+ )}
111
+ </FixedSizeList>
112
+ ```
113
+
114
+ ### 注意
115
+
116
+ - 虚拟化内部**不做**昂贵计算(会每次滚动重跑)
117
+ - 键盘导航 / 焦点管理要和虚拟化库的 `scrollToItem` 配合
118
+ - a11y:若丢失语义,需补 `role="list"` + `role="listitem"`
119
+
120
+ ## 5. 懒加载(代码拆分)
121
+
122
+ ### 路由级
123
+
124
+ ```tsx
125
+ const UserProfilePage = lazy(() => import('./pages/UserProfile'));
126
+
127
+ <Suspense fallback={<PageSkeleton />}>
128
+ <Route path="/user/:id" element={<UserProfilePage />} />
129
+ </Suspense>
130
+ ```
131
+
132
+ ### 组件级(非首屏或条件渲染)
133
+
134
+ ```tsx
135
+ const HeavyChart = lazy(() => import('./HeavyChart'));
136
+ {showChart && (
137
+ <Suspense fallback={<Spinner />}>
138
+ <HeavyChart data={data} />
139
+ </Suspense>
140
+ )}
141
+ ```
142
+
143
+ ### 图片懒加载(浏览器原生)
144
+
145
+ ```tsx
146
+ <img
147
+ src={src}
148
+ loading="lazy" // 原生懒加载
149
+ decoding="async" // 异步解码
150
+ width={800}
151
+ height={600} // 显式尺寸防 CLS
152
+ alt={t('...')}
153
+ />
154
+ ```
155
+
156
+ ### IntersectionObserver(视口进入再加载其他资源)
157
+
158
+ ```tsx
159
+ const ref = useRef<HTMLDivElement>(null);
160
+ useEffect(() => {
161
+ const obs = new IntersectionObserver((entries) => {
162
+ if (entries[0].isIntersecting) {
163
+ // 触发加载
164
+ obs.disconnect();
165
+ }
166
+ });
167
+ if (ref.current) obs.observe(ref.current);
168
+ return () => obs.disconnect();
169
+ }, []);
170
+ ```
171
+
172
+ ## 6. Bundle 优化
173
+
174
+ ### 构建层
175
+
176
+ - **Tree-shaking**:ESM 导入(`import { x } from 'lib'`),避免整库引入(`import _ from 'lodash'` → 改 `import debounce from 'lodash/debounce'` 或用 `lodash-es`)
177
+ - **代码拆分**:
178
+ - 路由级 lazy(如上)
179
+ - 动态 import 大依赖(`charting`, `PDF`, `编辑器`)
180
+ - **Polyfill 按需**:`@vitejs/plugin-legacy` / `core-js` + browserslist
181
+ - **压缩**:gzip + brotli(后端配置)
182
+
183
+ ### 监测
184
+
185
+ - **`rollup-plugin-visualizer`** / **`webpack-bundle-analyzer`**:发布前看 bundle 组成
186
+ - **`size-limit`**:CI 门禁,首屏 JS 超 10% 阻断(见 `feature-tasks-frontend` 7.5)
187
+
188
+ ### 依赖替代
189
+
190
+ | 重 | 轻替代 |
191
+ | --- | --- |
192
+ | `moment` | `date-fns` / `dayjs` |
193
+ | `lodash` 全量 | `lodash-es` 按需 / 原生 ES |
194
+ | `axios`(若只用基础) | 原生 `fetch` + 小封装 |
195
+ | `jquery` | 移除 |
196
+
197
+ ## 7. 图片与媒体
198
+
199
+ - **尺寸属性必填**(`width` / `height`):防 CLS
200
+ - **srcset + sizes**:响应式图片
201
+ ```html
202
+ <img
203
+ srcset="img-480.webp 480w, img-960.webp 960w, img-1440.webp 1440w"
204
+ sizes="(max-width: 768px) 100vw, 50vw"
205
+ src="img-960.webp"
206
+ />
207
+ ```
208
+ - **格式**:优先 `webp` / `avif`,降级 `png` / `jpg`
209
+ - **视频**:`preload="none"` 默认不加载;`poster` 属性提供静态占位
210
+ - **字体**:`font-display: swap` 防止白屏
211
+
212
+ ## 8. 防抖 / 节流
213
+
214
+ ### 何时用
215
+
216
+ | 场景 | 用 | 时长 |
217
+ | --- | --- | --- |
218
+ | 输入搜索 | `debounce` | 300ms 起步 |
219
+ | 按钮防重复点击 | `debounce` | 500ms |
220
+ | 窗口 resize / scroll | `throttle` | 100-200ms |
221
+ | 拖拽 / 滚动加载 | `throttle` | 16ms(一帧) |
222
+
223
+ ### React 实现
224
+
225
+ ```tsx
226
+ import { useMemo } from 'react';
227
+ import debounce from 'lodash/debounce';
228
+
229
+ // 稳定引用
230
+ const onSearch = useMemo(
231
+ () => debounce((q: string) => doSearch(q), 300),
232
+ []
233
+ );
234
+
235
+ useEffect(() => () => onSearch.cancel(), [onSearch]); // 卸载清理
236
+ ```
237
+
238
+ `useDeferredValue` / `useTransition`(React 18+)可替代部分场景:
239
+
240
+ ```tsx
241
+ const [query, setQuery] = useState('');
242
+ const deferredQuery = useDeferredValue(query); // 非紧急更新延迟
243
+ // 渲染用 deferredQuery
244
+ ```
245
+
246
+ ## 9. Context 性能
247
+
248
+ ### 禁用
249
+
250
+ - ❌ 把频繁变动的值(鼠标位置 / 滚动偏移 / 每帧更新)放 Context
251
+ - ❌ 大对象整体传 Context(导致订阅者全量重渲染)
252
+
253
+ ### 拆分
254
+
255
+ ```tsx
256
+ // 按变化频率切分
257
+ <UserInfoContext.Provider value={userInfo}> // 低频
258
+ <CursorPositionContext.Provider value={pos}> // 高频 → 用 ref + 订阅
259
+ ...
260
+ </CursorPositionContext.Provider>
261
+ </UserInfoContext.Provider>
262
+ ```
263
+
264
+ 高频值用 ref + 手动订阅 / `zustand` / `jotai` 等外部状态库。
265
+
266
+ ## 10. 诊断工具链
267
+
268
+ | 工具 | 用途 |
269
+ | --- | --- |
270
+ | Lighthouse | 线下评估 CWV |
271
+ | `web-vitals` 库 | 线上上报 |
272
+ | React DevTools Profiler | 重渲染分析 |
273
+ | Chrome DevTools Performance | Flame chart + Long Task |
274
+ | Chrome DevTools Coverage | 未使用代码比例 |
275
+ | `why-did-you-render` | 意外重渲染告警 |
276
+ | `size-limit` | CI bundle 门禁 |
277
+ | `rollup-plugin-visualizer` | Bundle 组成可视化 |
278
+
279
+ ## 反模式(禁止)
280
+
281
+ - ❌ 默认所有组件套 `memo` / 所有函数套 `useCallback`(认知负担 > 收益)
282
+ - ❌ 长列表不虚拟化(≥ 200 节点)
283
+ - ❌ 首屏加载所有路由代码
284
+ - ❌ 全量 import 大库(`lodash` / `moment`)
285
+ - ❌ 图片无 `width/height` 导致 CLS
286
+ - ❌ Context 放频繁变动的值
287
+ - ❌ 搜索 / scroll handler 不防抖 / 节流
288
+ - ❌ 声称"性能 OK"而没 Lighthouse / 实测数据
289
+
290
+ ## 质量门禁
291
+
292
+ - [ ] CWV 线上 p75 达标(LCP/INP/CLS 均 Good)
293
+ - [ ] 长列表虚拟化(或 ADR 豁免)
294
+ - [ ] 首屏 JS 增量 < 10%(`size-limit` CI)
295
+ - [ ] 图片有 `width/height` + `loading="lazy"`
296
+ - [ ] 无全量导入大库
297
+ - [ ] Context 无高频变动值
298
+ - [ ] 搜索 handler 防抖 ≥ 300ms
299
+ - [ ] Lighthouse score ≥ 90(性能)