尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

前端性能巡检怎么落地:把 LCP、长任务和包体预算接进 CI

前端性能巡检怎么落地:把 LCP、长任务和包体预算接进 CI 前端性能巡检怎么落地把 LCP、长任务和包体预算接进 CI前端性能巡检别迷信 Lighthouse 总分。按页面固定设备与网络多跑几次看 LCP、长任务和资源体积的分位数再跟 RUM 分组数据对照。1. 人工巡检的盲区别让“平均值”骗了你很多团队在做日常性能巡检时最常犯的错误就是看 Lighthouse 跑出来的平均分。本地 Lighthouse 的网络与 CPU 条件通常好于部分用户设备。巡检应固定浏览器、网络与 CPU 仿真多次运行后取分位数实验室数据再与按页面和设备分组的 RUM 数据对照。不要只看平均值。实验室数据可使用多次运行的分位数真实用户数据再按页面、设备和网络分组分析。P90 或 P95 是否合适取决于样本量和告警目标。2. 自动化性能巡检脚本实现可用 Playwright 做资源体积、LCP 与 CLS 的实验室巡检。INP 需要真实交互和页面生命周期处理建议使用web-vitals的参考实现采集真实用户数据不要把任意PerformanceEntry.duration当作 INP。下面是生产环境中可落地的自动化巡检 TypeScript 代码import { chromium, type Page } from playwright; export interface PerformanceBudget { maxLcpMs: number; maxCls: number; maxInpMs: number; maxTotalJsSizeKb: number; } export interface MetricReport { url: string; lcpMs: number; cls: number; inpMs: number; totalJsSizeKb: number; timestamp: number; passed: boolean; violations: string[]; } export const DefaultBudget: PerformanceBudget { maxLcpMs: 2000, maxCls: 0.1, maxInpMs: 200, maxTotalJsSizeKb: 500, }; export async function inspectPagePerformance( targetUrl: string, budget: PerformanceBudget DefaultBudget ): PromiseMetricReport { const browser await chromium.launch({ headless: true }); const context await browser.newContext({ viewport: { width: 1280, height: 720 }, // 仿真中端移动设备或中等网速环境 userAgent: Mozilla/5.0 (Linux; Android 10; Mobile) PerformanceInspector/1.0, }); const page await context.newPage(); // 1. 注入性能指标收集器 await page.addInitScript(() { (window as any).__perfMetrics { lcp: 0, cls: 0, }; // 监听 LCP new PerformanceObserver((entryList) { const entries entryList.getEntries(); const lastEntry entries[entries.length - 1]; if (lastEntry) { (window as any).__perfMetrics.lcp lastEntry.startTime; } }).observe({ type: largest-contentful-paint, buffered: true }); // 监听 CLS let clsValue 0; new PerformanceObserver((entryList) { for (const entry of entryList.getEntries() as any[]) { if (!entry.hadRecentInput) { clsValue entry.value; (window as any).__perfMetrics.cls clsValue; } } }).observe({ type: layout-shift, buffered: true }); }); // 2. 访问目标页面并等待网络空闲 await page.goto(targetUrl, { waitUntil: networkidle }); // 触发基本交互此示例不计算 INPINP 应通过 web-vitals 在真实用户侧采集。 await page.mouse.move(100, 100); await page.evaluate(() window.scrollBy(0, 300)); await page.waitForTimeout(1000); // 3. 计算已加载 JS 资源总体积 const jsSizeByte await page.evaluate(() { const resources performance.getEntriesByType(resource) as PerformanceResourceTiming[]; return resources .filter((r) r.initiatorType script || r.name.endsWith(.js)) .reduce((sum, r) sum (r.transferSize || 0), 0); }); const rawMetrics await page.evaluate(() (window as any).__perfMetrics); await browser.close(); const lcpMs Math.round(rawMetrics.lcp || 0); const cls Number((rawMetrics.cls || 0).toFixed(3)); const totalJsSizeKb Math.round(jsSizeByte / 1024); // 4. 比对性能预算 const violations: string[] []; if (lcpMs budget.maxLcpMs) { violations.push(LCP 耗时 ${lcpMs}ms 超出预算门禁 (${budget.maxLcpMs}ms)); } if (cls budget.maxCls) { violations.push(CLS 偏移值 ${cls} 超出预算门禁 (${budget.maxCls})); } if (totalJsSizeKb budget.maxTotalJsSizeKb) { violations.push(JS 资源总体积 ${totalJsSizeKb}KB 超出预算门禁 (${budget.maxTotalJsSizeKb}KB)); } return { url: targetUrl, lcpMs, cls, inpMs: 0, // 未采集时不要将该值用于 INP 预算判断。 totalJsSizeKb, timestamp: Date.now(), passed: violations.length 0, violations, }; }4. 总结巡检指标用于发现回归而不是替代真实用户监测。将 LCP、CLS、资源体积的实验室数据与 RUM 数据分开记录超出预算时附上 trace 和变更范围团队才更容易处理告警。
返回列表