Lighthouse 与性能审计

概述

在性能优化的过程中,一个常见的困境是「我不知道问题在哪里」。前端性能问题的诊断往往需要专业的工具和深厚的经验。作为 Google 开源的自动化网页质量审计工具,Lighthouse 提供了一套全面的性能检测能力,能够自动分析页面的加载性能、可访问性问题、最佳实践遵循情况和 SEO 表现。

Lighthouse 的价值不仅在于发现问题,更在于它提供了一个标准化的评估框架。当你报告「这个页面加载很慢」时,团队可能会争论不休。但当你报告「Lighthouse 性能评分是 65,我们需要达到 90」时,目标就变得清晰可量化。这种标准化使得性能优化从主观感受变成了可以追踪的工程指标。

Lighthouse 的审计结果可以分为几个主要类别:Performance(性能)衡量页面加载速度和响应性;Accessibility(可访问性)检查页面是否对残障人士友好;Best Practices(最佳实践)评估开发实践是否符合现代 Web 标准;SEO 检查页面是否对搜索引擎友好。每个类别都会产生一个 0-100 的评分。

本节将深入讲解 Lighthouse 的使用方法,包括多种运行方式、配置选项,以及如何解读审计报告中的各种数据。我们会探讨如何在 CI/CD 流水线中集成 Lighthouse 实现持续性能监控,以及如何编写自定义审计来检测特定问题。

目标

  • 掌握 Lighthouse 的多种运行方式及配置选项
  • 深入理解审计报告中各项指标的含义和优先级
  • 学会根据审计结果制定优化方案并验证效果
  • 掌握 Lighthouse CI 的集成与自动化审计流程

知识体系

1. Lighthouse 运行方式

Lighthouse 可以在多种环境中运行:Chrome DevTools、命令行、Node.js API。每种方式适合不同的使用场景。

Chrome DevTools

对于日常开发和快速诊断,Chrome DevTools 内置的 Lighthouse 面板是最便捷的方式。打开 DevTools(F12 或 Cmd+Option+I),切换到 Lighthouse 面板,选择审计类别后点击「Analyze page load」即可。

DevTools 中的 Lighthouse 提供了直观的可视化报告,包括性能评分、性能指标详情、优化建议列表等。对于初学者来说,这是最容易上手的方式。

CLI 方式

Lighthouse CLI 适合在脚本中使用和自动化场景。通过 npm 全局安装后,可以对任何 URL 运行审计。

# 安装 Lighthouse CLI
npm install -g lighthouse

# 基本运行
lighthouse https://example.com --output=html --output-path=./report.html

# 指定设备模式
lighthouse https://example.com --preset=desktop
lighthouse https://example.com --preset=perf  # 仅性能审计

# JSON 输出用于程序化处理
lighthouse https://example.com --output=json --output-path=./report.json

# 自定义 throttling
lighthouse https://example.com \
  --throttling.cpuSlowdownMultiplier=4 \
  --throttling.downloadThroughputKbps=1600 \
  --throttling.uploadThroughputKbps=750 \
  --throttling.rttMs=150

preset 参数可以快速选择预配置的审计集。perf 预设只运行性能相关的审计,运行速度更快。throttling 参数允许你精确控制模拟的网络和 CPU 条件。

Node API

对于需要程序化控制的高级场景,可以使用 Lighthouse 的 Node.js API。

import lighthouse from 'lighthouse';
import * as chromeLauncher from 'chrome-launcher';

async function runAudit(url) {
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });

  const options = {
    logLevel: 'info',
    output: 'json',
    port: chrome.port,
    onlyCategories: ['performance'],
    formFactor: 'mobile',
    screenEmulation: {
      mobile: true,
      width: 375,
      height: 667,
      deviceScaleFactor: 2,
    },
  };

  const result = await lighthouse(url, options);
  const report = result.report;
  const lhr = result.lhr; // Lighthouse Result Object

  console.log(`Performance Score: ${lhr.categories.performance.score * 100}`);

  await chrome.kill();
  return lhr;
}

通过 Node API,你可以精确控制审计的各项参数,并将结果集成到自己的监控系统中。

2. 审计报告深度解读

理解 Lighthouse 报告的结构和各项数据的含义,是制定有效优化方案的前提。

Performance 评分计算

Lighthouse Performance 评分(0-100)是由多个性能指标加权计算得出的。v11 版本的评分算法使用以下权重:

指标 权重 说明
FCP (First Contentful Paint) 10% 首次内容绘制时间
LCP (Largest Contentful Paint) 25% 最大内容绘制时间
TBT (Total Blocking Time) 30% 总阻塞时间
CLS (Cumulative Layout Shift) 25% 累积布局偏移
SI (Speed Index) 10% 速度指数

权重反映了每个指标对用户体验的重要性。LCP 和 CLS 各占 25% 是因为它们直接映射 Core Web Vitals。TBT 占 30% 是因为主线程阻塞对交互性的影响最为显著。

// 解析 Lighthouse 报告中的关键数据
function analyzeReport(lhr) {
  const { audits, categories } = lhr;

  // 获取性能评分
  const perfScore = categories.performance.score * 100;

  // 获取核心指标
  const metrics = {
    fcp: audits['first-contentful-paint'].numericValue,
    lcp: audits['largest-contentful-paint'].numericValue,
    tbt: audits['total-blocking-time'].numericValue,
    cls: audits['cumulative-layout-shift'].numericValue,
    si: audits['speed-index'].numericValue,
  };

  // 获取优化建议
  const opportunities = Object.values(audits)
    .filter((audit) => audit.details?.type === 'opportunity')
    .sort((a, b) => (b.details.overallSavingsMs || 0) - (a.details.overallSavingsMs || 0));

  return { perfScore, metrics, opportunities };
}

opportunities 数组包含了所有优化建议,按预估节省时间排序。Lighthouse 会为每个优化建议标注预估的改进效果(以毫秒为单位),帮助你优先处理最高价值的优化。

Opportunities(优化机会)

Lighthouse 的 Opportunities 部分提供了具体的优化建议,每条建议都包含预估收益和具体实施方案。常见的优化机会包括:

Eliminate render-blocking resources(消除阻塞渲染的资源):CSS 和 JavaScript 文件如果放在文档头部,会阻塞页面的首次渲染。将非关键 CSS 改为异步加载、将 JavaScript 移到文档末尾可以显著改善 FCP。

Properly size images(正确调整图片尺寸):Serving images that are larger than necessary wastes data. Using responsive images with srcset and correct sizes attributes ensures users download only what they need for their viewport.

Defer offscreen images(延迟加载屏幕外图片):对非首屏图片使用 loading="lazy" 可以减少首屏加载时间。

Minify CSS/JavaScript(压缩 CSS/JavaScript):移除未使用的代码、缩短变量名、压缩空格可以减小资源体积。

Serve images in next-gen formats(使用现代图片格式):WebP 和 AVIF 比 JPEG 和 PNG 有更好的压缩率,可以显著减少图片体积。

Diagnostics(诊断信息)

Diagnostics 部分提供了更详细的性能诊断信息,帮助你理解页面的整体性能特征:

Avoid enormous network payloads(避免过大的网络负载):总资源传输量过大会延长加载时间。

Avoid long main-thread tasks(避免长任务):主线程任务超过 50ms 会被视为「长任务」,会阻塞用户交互。

Minimize critical request depth(最小化关键请求深度):关键请求链越长,加载延迟越大。

Avoid excessive DOM size(避免过大的 DOM):DOM 节点过多会增加布局和渲染的计算成本。

3. Lighthouse CI

将 Lighthouse 集成到 CI/CD 流水线中,可以确保每次代码变更都不会引入性能退化。

基础配置

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: [
        'http://localhost:3000/',
        'http://localhost:3000/products',
        'http://localhost:3000/checkout',
      ],
      numberOfRuns: 3,
      startServerCommand: 'npm run start',
      startServerReadyPattern: 'ready on',
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['warn', { minScore: 0.9 }],
        'categories:best-practices': ['warn', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-blocking-time': ['error', { maxNumericValue: 300 }],
      },
    },
    upload: {
      target: 'lhci',
      serverBaseUrl: 'https://lhci.example.com',
      token: process.env.LHCI_BUILD_TOKEN,
    },
  },
};

assertions 部分定义了断言规则。当评分或指标超过阈值时,error 级别会导致 CI 失败,warn 级别只产生警告。numberOfRuns: 3 表示对每个 URL 运行 3 次审计,取中位数作为最终结果,以减少测量波动。

GitHub Actions 集成

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run build

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v11
        with:
          configPath: './lighthouserc.js'
          uploadArtifacts: true
          temporaryPublicStorage: true

      - name: Format Lighthouse Score
        uses: jwalton/gh-find-current-pr@v1
        id: findPr

      - name: Comment PR with Results
        if: steps.findPr.outputs.number
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          number: ${{ steps.findPr.outputs.number }}
          path: lighthouse-results.md

这个工作流在每次 PR 时自动运行 Lighthouse CI,并将结果评论到 PR 中。团队成员可以在 PR 页面直接看到性能评分和变化,无需额外的监控平台。

4. 自定义审计

Lighthouse 提供了扩展机制,允许你编写自定义审计来检测特定问题。

// custom-audit.js
import { Audit } from 'lighthouse';

class CustomBundleSizeAudit extends Audit {
  static get meta() {
    return {
      id: 'custom-bundle-size',
      title: 'JavaScript bundle size is reasonable',
      failureTitle: 'JavaScript bundle size is too large',
      description: 'Large bundles increase load time and hurt performance.',
      requiredArtifacts: ['devtoolsLogs', 'traces'],
    };
  }

  static get defaultOptions() {
    return {
      maxBundleSize: 250 * 1024, // 250KB
    };
  }

  static audit(artifacts, context) {
    const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
    // 分析 JavaScript 资源大小
    const jsRequests = devtoolsLog.filter(
      (entry) => entry.type === 'Script'
    );

    const totalSize = jsRequests.reduce(
      (sum, req) => sum + (req.transferSize || 0),
      0
    );

    const passed = totalSize <= context.options.maxBundleSize;

    return {
      score: passed ? 1 : 0,
      numericValue: totalSize,
      displayValue: `Total JS: ${(totalSize / 1024).toFixed(1)} KB`,
    };
  }
}

export default CustomBundleSizeAudit;

自定义审计需要继承 Audit 类并实现 metaaudit 方法。meta 定义审计的元信息(ID、标题、描述等),audit 实现具体的检测逻辑。

5. 常见陷阱与注意事项

Lighthouse 评分波动

Lighthouse 运行之间存在一定的波动,这是由于网络条件、CPU 负载等环境因素造成的。为了获得更稳定的评分,建议多次运行取中位数。

// 通过多次运行取中位数来减少波动
async function getStableScore(url, runs = 5) {
  const scores = [];

  for (let i = 0; i < runs; i++) {
    const result = await runAudit(url);
    scores.push(result.categories.performance.score);
  }

  scores.sort((a, b) => a - b);
  const median = scores[Math.floor(scores.length / 2)];

  return {
    median: median * 100,
    min: Math.min(...scores) * 100,
    max: Math.max(...scores) * 100,
    scores: scores.map((s) => s * 100),
  };
}

Lab vs Field 数据差异

Lighthouse 属于 Lab Data(实验室数据),它使用模拟的设备和网络条件进行测试。这与真实用户的 Field Data(现场数据)可能存在显著差异。

维度 Lab Data Field Data
测量环境 模拟设备与网络 真实用户设备与网络
数据一致性 高(可复现) 低(因用户环境各异)
适用场景 开发调试、CI/CD 监控真实用户体验
INP 指标 不支持(使用 TBT 替代) 支持

一个重要的差异是 INP 指标。Lighthouse 使用 TBT(Total Blocking Time)作为替代指标,因为它无法模拟真实的用户交互。而 Field Data 通过 web-vitals 库采集的是真实的用户交互延迟。

因此,在性能优化时应该同时关注 Lab Data 和 Field Data。Lab Data 适合开发阶段的快速验证和 CI 中的回归检测,Field Data 则反映真实用户的实际体验。


实战练习

练习 1:完整审计报告解读

选择一个实际网站或项目,使用 Lighthouse 进行完整审计。逐项分析每个 Opportunity 和 Diagnostic,理解每个问题的原因和推荐解决方案。将高优先级的优化建议按预估收益排序,制定优化计划。

练习 2:Lighthouse CI 流水线

在项目中配置 Lighthouse CI,设置合理的性能阈值断言。配置 GitHub Actions 工作流,确保每次 PR 都自动运行审计并评论结果。尝试设置渐进式的阈值(随着项目成熟逐步提高要求)。

练习 3:自定义审计插件

编写一个自定义 Lighthouse 审计插件,检测页面是否存在过多的第三方脚本、是否正确使用图片格式、是否有未压缩的资源等。将自定义审计集成到项目 CI 中。


延展阅读


关键术语

术语 解释
Lighthouse Google 开源的网页质量审计工具
FCP First Contentful Paint,首次内容绘制
TBT Total Blocking Time,总阻塞时间
SI Speed Index,速度指数
LHCI Lighthouse CI,Lighthouse 持续集成工具
Throttling 网络和 CPU 限速模拟
Opportunity Lighthouse 报告中的优化机会
Diagnostic Lighthouse 报告中的诊断信息