commit bb0a685145cf1968f6b833224948047434d913f0 Author: wehub-skill-sync Date: Mon Jul 13 21:36:50 2026 +0800 chore: import zh skill performance-optimization diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..b7a8663 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,9 @@ +# WeHub 来源说明 + +- Skill 名称:`performance-optimization` +- 中文类目:Web 性能核心优化 +- 上游仓库:`addyosmani__agent-skills` +- 上游路径:`skills/performance-optimization/SKILL.md` +- 上游链接:https://github.com/addyosmani/agent-skills/blob/HEAD/skills/performance-optimization/SKILL.md +- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理 +- 原作者、版权和许可证信息以上游仓库为准 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ea72a01 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,349 @@ +--- +name: performance-optimization +description: 优化应用程序性能。当存在性能需求、怀疑性能回归,或需要改进 Core Web Vitals 及加载时间时使用。当性能分析揭示出需要修复的瓶颈时使用。 +--- + +# 性能优化 + +## 概述 + +先测量,再优化。不经过测量的性能工作等于猜测——而猜测会导致过早优化,增加复杂度却没有改善真正重要的指标。先做性能分析,找到真正的瓶颈,修复它,再次测量。只优化测量证明确实重要的部分。 + +## 何时使用 + +- 规格说明中存在性能要求(加载时间预算、响应时间 SLA) +- 用户或监控报告行为缓慢 +- Core Web Vitals 分数低于阈值 +- 你怀疑某个变更引入了性能退化 +- 构建处理大数据集或高流量的功能时 + +**何时不应使用:** 在获得问题证据之前不要优化。过早优化会增加复杂度,其成本超过带来的性能收益。 + +## Core Web Vitals 目标值 + +| 指标 | 良好 | 待改善 | 差 | +|--------|------|-------------------|------| +| **LCP**(最大内容绘制) | ≤ 2.5s | ≤ 4.0s | > 4.0s | +| **INP**(下次绘制的交互延迟) | ≤ 200ms | ≤ 500ms | > 500ms | +| **CLS**(累积布局偏移) | ≤ 0.1 | ≤ 0.25 | > 0.25 | + +## 优化工作流程 + +``` +1. 测量 → 使用真实数据建立基线 +2. 识别 → 找到真正的瓶颈(而非假设的瓶颈) +3. 修复 → 针对特定瓶颈进行修复 +4. 验证 → 再次测量,确认改善效果 +5. 防护 → 添加监控或测试以防止退化 +``` + +### 步骤 1:测量 + +两种互补的方式——两者都使用: + +- **合成测试(Lighthouse、DevTools Performance 面板):** 受控环境,可复现。最适合 CI 回归检测和隔离特定问题。 +- **真实用户监控(web-vitals 库、CrUX):** 真实条件下的真实用户数据。需要用它来验证修复是否确实改善了用户体验。 + +**前端:** +```bash +# 合成测试:Chrome DevTools 中的 Lighthouse(或 CI) +# Chrome DevTools → Performance 面板 → 录制 +# Chrome DevTools MCP → 性能追踪 + +# 真实用户监控:代码中使用 Web Vitals 库 +import { onLCP, onINP, onCLS } from 'web-vitals'; + +onLCP(console.log); +onINP(console.log); +onCLS(console.log); +``` + +**后端:** +```bash +# 响应时间日志记录 +# 应用性能监控(APM) +# 带计时信息的数据库查询日志 + +# 简单计时 +console.time('db-query'); +const result = await db.query(...); +console.timeEnd('db-query'); +``` + +### 从何处开始测量 + +根据症状决定优先测量的内容: + +``` +什么变慢了? +├── 首次页面加载 +│ ├── 打包文件过大? --> 测量打包体积,检查代码分割 +│ ├── 服务器响应慢? --> 在 DevTools Network 瀑布图中测量 TTFB +│ │ ├── DNS 耗时长? --> 为已知来源添加 dns-prefetch / preconnect +│ │ ├── TCP/TLS 耗时长? --> 启用 HTTP/2,检查边缘节点部署,保持长连接 +│ │ └── 等待(服务器)耗时长? --> 对后端做性能分析,检查查询和缓存 +│ └── 渲染阻塞资源? --> 检查网络瀑布图中 CSS/JS 的阻塞情况 +├── 交互感觉卡顿 +│ ├── 点击时界面冻结? --> 分析主线程,查找长任务(>50ms) +│ ├── 表单输入延迟? --> 检查重复渲染、受控组件的开销 +│ └── 动画卡顿? --> 检查布局抖动、强制回流 +├── 页面导航后 +│ ├── 数据加载? --> 测量 API 响应时间,检查瀑布请求链 +│ └── 客户端渲染? --> 分析组件渲染时间,检查是否存在 N+1 请求 +└── 后端 / API + ├── 单个端点慢? --> 分析数据库查询,检查索引 + ├── 所有端点都慢? --> 检查连接池、内存、CPU + └── 间歇性慢? --> 检查锁竞争、GC 暂停、外部依赖 +``` + +### 步骤 2:识别瓶颈 + +按类别划分的常见瓶颈: + +**前端:** + +| 症状 | 可能原因 | 调查方法 | +|---------|-------------|---------------| +| LCP 慢 | 大图、渲染阻塞资源、服务器慢 | 检查网络瀑布图、图片尺寸 | +| CLS 高 | 未指定尺寸的图片、延迟加载的内容、字体偏移 | 检查布局偏移归因 | +| INP 差 | 主线程上繁重的 JavaScript、大型 DOM 更新 | 检查 Performance 追踪中的长任务 | +| 初始加载慢 | 打包文件过大、网络请求过多 | 检查打包体积、代码分割 | + +**后端:** + +| 症状 | 可能原因 | 调查方法 | +|---------|-------------|---------------| +| API 响应慢 | N+1 查询、缺少索引、查询未优化 | 检查数据库查询日志 | +| 内存增长 | 引用泄漏、无界缓存、大数据包 | 堆快照分析 | +| CPU 飙升 | 同步重型计算、正则回溯 | CPU 性能分析 | +| 高延迟 | 缺少缓存、重复计算、网络跳数 | 在整个堆栈中追踪请求 | + +### 步骤 3:修复常见反模式 + +#### N+1 查询(后端) + +```typescript +// 错误:N+1 —— 每个任务都做一次查询来获取所属用户 +const tasks = await db.tasks.findMany(); +for (const task of tasks) { + task.owner = await db.users.findUnique({ where: { id: task.ownerId } }); +} + +// 正确:单次查询包含关联 +const tasks = await db.tasks.findMany({ + include: { owner: true }, +}); +``` + +#### 无限制的数据获取 + +```typescript +// 错误:获取所有记录 +const allTasks = await db.tasks.findMany(); + +// 正确:分页并限制条数 +const tasks = await db.tasks.findMany({ + take: 20, + skip: (page - 1) * 20, + orderBy: { createdAt: 'desc' }, +}); +``` + +#### 缺少图片优化(前端) + +```html + + + + + + + + + + + + + 主图描述 + + + +内容图片描述 +``` + +#### 不必要的重复渲染(React) + +```tsx +// 错误:每次渲染都创建新对象,导致子组件重复渲染 +function TaskList() { + return ; +} + +// 正确:稳定的引用 +const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const; +function TaskList() { + return ; +} + +// 对开销大的组件使用 React.memo +const TaskItem = React.memo(function TaskItem({ task }: Props) { + return
{/* 开销大的渲染 */}
; +}); + +// 对开销大的计算使用 useMemo +function TaskStats({ tasks }: Props) { + const stats = useMemo(() => calculateStats(tasks), [tasks]); + return
{stats.completed} / {stats.total}
; +} +``` + +#### 打包体积过大 + +```typescript +// 现代打包工具(Vite、webpack 5+)通过 Tree Shaking 自动处理具名导入, +// 前提是依赖以 ESM 形式发布且 package.json 中标记了 `sideEffects: false`。 +// 在修改导入方式之前先做性能分析——真正的收益来自代码分割和懒加载。 + +// 正确:对重量级、不常用的功能使用动态导入 +const ChartLibrary = lazy(() => import('./ChartLibrary')); + +// 正确:路由级别的代码分割,包裹在 Suspense 中 +const SettingsPage = lazy(() => import('./pages/Settings')); + +function App() { + return ( + }> + + + ); +} +``` + +#### 缺少缓存(后端) + +```typescript +// 缓存频繁读取、极少变更的数据 +const CACHE_TTL = 5 * 60 * 1000; // 5 分钟 +let cachedConfig: AppConfig | null = null; +let cacheExpiry = 0; + +async function getAppConfig(): Promise { + if (cachedConfig && Date.now() < cacheExpiry) { + return cachedConfig; + } + cachedConfig = await db.config.findFirst(); + cacheExpiry = Date.now() + CACHE_TTL; + return cachedConfig; +} + +// 静态资源的 HTTP 缓存头 +app.use('/static', express.static('public', { + maxAge: '1y', // 缓存 1 年 + immutable: true, // 永不重新验证(文件名中使用内容哈希) +})); + +// API 响应的 Cache-Control +res.set('Cache-Control', 'public, max-age=300'); // 5 分钟 +``` + +## 性能预算 + +设定预算并执行: + +``` +JavaScript 打包文件:< 200KB gzip 压缩后(初始加载) +CSS:< 50KB gzip 压缩后 +图片:< 200KB 每张(首屏以上区域) +字体:< 100KB 总共 +API 响应时间:< 200ms(p95) +可交互时间:< 3.5s(4G 网络下) +Lighthouse 性能评分:≥ 90 +``` + +**在 CI 中执行:** +```bash +# 打包体积检查 +npx bundlesize --config bundlesize.config.json + +# Lighthouse CI +npx lhci autorun +``` + +## 参见 + +详细的性能检查清单、优化命令及反模式参考,请参见 `references/performance-checklist.md`。 + +## 常见借口 + +| 借口 | 现实 | +|---|---| +| "我们以后再优化" | 性能债会不断累积。立即修复明显的反模式,微优化可以推迟。 | +| "在我机器上跑得很快" | 你的机器不是用户的机器。在代表性的硬件和网络条件下做性能分析。 | +| "这个优化是显而易见的" | 如果你没有测量,你就不知道。先做性能分析。 | +| "用户不会注意到 100ms 的差异" | 研究表明 100ms 的延迟会影响转化率。用户比你以为的更敏感。 | +| "框架会处理好性能" | 框架可以防止一些问题,但无法修复 N+1 查询或过大的打包文件。 | + +## 危险信号 + +- 没有性能分析数据支撑的优化 +- 数据获取中的 N+1 查询模式 +- 没有分页的列表接口 +- 未指定尺寸、未使用懒加载或响应式尺寸的图片 +- 未经审核不断增大的打包体积 +- 生产环境中没有性能监控 +- 到处使用 `React.memo` 和 `useMemo`(过度使用和不足使用同样有害) + +## 验证 + +每次与性能相关的变更后: + +- [ ] 存在变更前后的测量数据(具体数值) +- [ ] 已识别并修复了具体的瓶颈 +- [ ] Core Web Vitals 在"良好"阈值范围内 +- [ ] 打包体积没有显著增大 +- [ ] 新增的数据获取代码中没有 N+1 查询 +- [ ] 性能预算在 CI 中通过(如果已配置) +- [ ] 现有测试仍然通过(优化没有破坏功能行为)