@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.
- package/README.md +263 -0
- package/bundles.yaml +104 -0
- package/design-handoff/.gitkeep +0 -0
- package/design-handoff/figma-to-code/SKILL.md +265 -0
- package/design-handoff/ue-mcp/SKILL.md +184 -0
- package/frontend/.gitkeep +0 -0
- package/frontend/a11y-web/SKILL.md +169 -0
- package/frontend/api-contract-ts/SKILL.md +275 -0
- package/frontend/css-bem/SKILL.md +268 -0
- package/frontend/design-frontend/SKILL.md +163 -0
- package/frontend/dor-dod-frontend/SKILL.md +114 -0
- package/frontend/feature-tasks-frontend/SKILL.md +246 -0
- package/frontend/i18n/SKILL.md +296 -0
- package/frontend/nfr-web/SKILL.md +258 -0
- package/frontend/performance-web/SKILL.md +299 -0
- package/frontend/react-components/SKILL.md +211 -0
- package/frontend/react-naming/SKILL.md +224 -0
- package/frontend/review-frontend/SKILL.md +126 -0
- package/frontend/security-web/SKILL.md +286 -0
- package/frontend/spec-frontend/SKILL.md +141 -0
- package/frontend/testing-web/SKILL.md +252 -0
- package/frontend/typescript/SKILL.md +219 -0
- package/meta/.gitkeep +0 -0
- package/meta/philosophy/SKILL.md +221 -0
- package/package.json +24 -0
- package/quality/.gitkeep +0 -0
- package/quality/a11y-principles/SKILL.md +155 -0
- package/quality/code-hygiene/SKILL.md +126 -0
- package/quality/release/SKILL.md +209 -0
- package/quality/security-owasp/SKILL.md +157 -0
- package/quality/testing-pyramid/SKILL.md +173 -0
- package/sdlc/.gitkeep +0 -0
- package/sdlc/archive/SKILL.md +267 -0
- package/sdlc/bugfix/SKILL.md +181 -0
- package/sdlc/bugfix-tasks/SKILL.md +232 -0
- package/sdlc/coding/SKILL.md +177 -0
- package/sdlc/design/SKILL.md +299 -0
- package/sdlc/dor-dod/SKILL.md +120 -0
- package/sdlc/feature/SKILL.md +162 -0
- package/sdlc/proposal/SKILL.md +271 -0
- package/sdlc/refactor/SKILL.md +220 -0
- package/sdlc/refactor-tasks/SKILL.md +265 -0
- package/sdlc/reviewing/SKILL.md +197 -0
- package/sdlc/spec/SKILL.md +235 -0
- package/sdlc/task/SKILL.md +116 -0
- package/sdlc/task-evidence/SKILL.md +178 -0
- package/sdlc/task-structure/SKILL.md +153 -0
- package/sdlc/task-tracking/SKILL.md +192 -0
- package/sdlc/verify/SKILL.md +181 -0
- package/system/.gitkeep +0 -0
- package/system/adr/SKILL.md +169 -0
- package/system/architecture/SKILL.md +182 -0
- package/system/glossary/SKILL.md +141 -0
- package/system/nfr-baseline/SKILL.md +156 -0
- 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(性能)
|