perf-lighthouse

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Lighthouse Audits

Lighthouse审计

CLI Quick Start

CLI快速入门

bash
undefined
bash
undefined

Install

Install

npm install -g lighthouse
npm install -g lighthouse

Basic audit

Basic audit

Mobile performance only (faster)

Mobile performance only (faster)

lighthouse https://example.com --preset=perf --form-factor=mobile
lighthouse https://example.com --preset=perf --form-factor=mobile

Output JSON for parsing

Output JSON for parsing

lighthouse https://example.com --output=json --output-path=./report.json
lighthouse https://example.com --output=json --output-path=./report.json

Output HTML report

Output HTML report

lighthouse https://example.com --output=html --output-path=./report.html
undefined
lighthouse https://example.com --output=html --output-path=./report.html
undefined

Common Flags

常用参数

bash
--preset=perf           # Performance only (skip accessibility, SEO, etc.)
--form-factor=mobile    # Mobile device emulation (default)
--form-factor=desktop   # Desktop
--throttling-method=devtools  # More accurate throttling
--only-categories=performance,accessibility  # Specific categories
--chrome-flags="--headless"   # Headless Chrome
bash
--preset=perf           # Performance only (skip accessibility, SEO, etc.)
--form-factor=mobile    # Mobile device emulation (default)
--form-factor=desktop   # Desktop
--throttling-method=devtools  # More accurate throttling
--only-categories=performance,accessibility  # Specific categories
--chrome-flags="--headless"   # Headless Chrome

Performance Budgets

性能预算

Create
budget.json
:
json
[
  {
    "resourceSizes": [
      { "resourceType": "script", "budget": 200 },
      { "resourceType": "image", "budget": 300 },
      { "resourceType": "stylesheet", "budget": 50 },
      { "resourceType": "total", "budget": 500 }
    ],
    "resourceCounts": [
      { "resourceType": "third-party", "budget": 5 }
    ],
    "timings": [
      { "metric": "interactive", "budget": 3000 },
      { "metric": "first-contentful-paint", "budget": 1500 },
      { "metric": "largest-contentful-paint", "budget": 2500 }
    ]
  }
]
Run with budget:
bash
lighthouse https://example.com --budget-path=./budget.json
创建
budget.json
文件:
json
[
  {
    "resourceSizes": [
      { "resourceType": "script", "budget": 200 },
      { "resourceType": "image", "budget": 300 },
      { "resourceType": "stylesheet", "budget": 50 },
      { "resourceType": "total", "budget": 500 }
    ],
    "resourceCounts": [
      { "resourceType": "third-party", "budget": 5 }
    ],
    "timings": [
      { "metric": "interactive", "budget": 3000 },
      { "metric": "first-contentful-paint", "budget": 1500 },
      { "metric": "largest-contentful-paint", "budget": 2500 }
    ]
  }
]
结合预算运行审计:
bash
lighthouse https://example.com --budget-path=./budget.json

Node API

Node API

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

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

  const result = await lighthouse(url, {
    port: chrome.port,
    onlyCategories: ['performance'],
    formFactor: 'mobile',
    throttling: {
      cpuSlowdownMultiplier: 4,
    },
  });

  await chrome.kill();

  const { performance } = result.lhr.categories;
  const { 'largest-contentful-paint': lcp } = result.lhr.audits;

  return {
    score: Math.round(performance.score * 100),
    lcp: lcp.numericValue,
  };
}
javascript
import lighthouse from 'lighthouse';
import * as chromeLauncher from 'chrome-launcher';

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

  const result = await lighthouse(url, {
    port: chrome.port,
    onlyCategories: ['performance'],
    formFactor: 'mobile',
    throttling: {
      cpuSlowdownMultiplier: 4,
    },
  });

  await chrome.kill();

  const { performance } = result.lhr.categories;
  const { 'largest-contentful-paint': lcp } = result.lhr.audits;

  return {
    score: Math.round(performance.score * 100),
    lcp: lcp.numericValue,
  };
}

GitHub Actions

GitHub Actions集成

yaml
undefined
yaml
undefined

.github/workflows/lighthouse.yml

.github/workflows/lighthouse.yml

name: Lighthouse
on: pull_request: push: branches: [main]
jobs: lighthouse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Build site
    run: npm ci && npm run build

  - name: Run Lighthouse
    uses: treosh/lighthouse-ci-action@v11
    with:
      urls: |
        http://localhost:3000
        http://localhost:3000/about
      budgetPath: ./budget.json
      uploadArtifacts: true
      temporaryPublicStorage: true
    env:
      LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
undefined
name: Lighthouse
on: pull_request: push: branches: [main]
jobs: lighthouse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Build site
    run: npm ci && npm run build

  - name: Run Lighthouse
    uses: treosh/lighthouse-ci-action@v11
    with:
      urls: |
        http://localhost:3000
        http://localhost:3000/about
      budgetPath: ./budget.json
      uploadArtifacts: true
      temporaryPublicStorage: true
    env:
      LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
undefined

Lighthouse CI (LHCI)

Lighthouse CI (LHCI)

For full CI integration with historical tracking:
bash
undefined
如需完整的CI集成并支持历史记录追踪:
bash
undefined

Install

Install

npm install -g @lhci/cli
npm install -g @lhci/cli

Initialize config

Initialize config

lhci wizard

Creates `lighthouserc.js`:

```javascript
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/about'],
      startServerCommand: 'npm run start',
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['warn', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 1500 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
      },
    },
    upload: {
      target: 'temporary-public-storage', // or 'lhci' for self-hosted
    },
  },
};
Run:
bash
lhci autorun
lhci wizard

生成`lighthouserc.js`配置文件:

```javascript
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/about'],
      startServerCommand: 'npm run start',
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['warn', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 1500 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
      },
    },
    upload: {
      target: 'temporary-public-storage', // or 'lhci' for self-hosted
    },
  },
};
运行审计:
bash
lhci autorun

Parse JSON Report

解析JSON报告

javascript
import fs from 'fs';

const report = JSON.parse(fs.readFileSync('./report.json'));

// Overall scores (0-1, multiply by 100 for percentage)
const scores = {
  performance: report.categories.performance.score,
  accessibility: report.categories.accessibility.score,
  seo: report.categories.seo.score,
};

// Core Web Vitals
const vitals = {
  lcp: report.audits['largest-contentful-paint'].numericValue,
  cls: report.audits['cumulative-layout-shift'].numericValue,
  fcp: report.audits['first-contentful-paint'].numericValue,
  tbt: report.audits['total-blocking-time'].numericValue,
};

// Failed audits
const failed = Object.values(report.audits)
  .filter(a => a.score !== null && a.score < 0.9)
  .map(a => ({ id: a.id, score: a.score, title: a.title }));
javascript
import fs from 'fs';

const report = JSON.parse(fs.readFileSync('./report.json'));

// Overall scores (0-1, multiply by 100 for percentage)
const scores = {
  performance: report.categories.performance.score,
  accessibility: report.categories.accessibility.score,
  seo: report.categories.seo.score,
};

// Core Web Vitals
const vitals = {
  lcp: report.audits['largest-contentful-paint'].numericValue,
  cls: report.audits['cumulative-layout-shift'].numericValue,
  fcp: report.audits['first-contentful-paint'].numericValue,
  tbt: report.audits['total-blocking-time'].numericValue,
};

// Failed audits
const failed = Object.values(report.audits)
  .filter(a => a.score !== null && a.score < 0.9)
  .map(a => ({ id: a.id, score: a.score, title: a.title }));

Compare Builds

对比不同构建版本

bash
undefined
bash
undefined

Save baseline

保存基准报告

lighthouse https://prod.example.com --output=json --output-path=baseline.json
lighthouse https://prod.example.com --output=json --output-path=baseline.json

Run on PR

在PR版本上运行审计

lighthouse https://preview.example.com --output=json --output-path=pr.json
lighthouse https://preview.example.com --output=json --output-path=pr.json

Compare (custom script)

对比报告(自定义脚本)

node compare-reports.js baseline.json pr.json

Simple comparison script:

```javascript
const baseline = JSON.parse(fs.readFileSync(process.argv[2]));
const pr = JSON.parse(fs.readFileSync(process.argv[3]));

const metrics = ['largest-contentful-paint', 'cumulative-layout-shift', 'total-blocking-time'];

metrics.forEach(metric => {
  const base = baseline.audits[metric].numericValue;
  const current = pr.audits[metric].numericValue;
  const diff = ((current - base) / base * 100).toFixed(1);
  const emoji = current <= base ? '✅' : '❌';
  console.log(`${emoji} ${metric}: ${diff}% (${base.toFixed(0)} → ${current.toFixed(0)})`);
});
node compare-reports.js baseline.json pr.json

简单对比脚本:

```javascript
const baseline = JSON.parse(fs.readFileSync(process.argv[2]));
const pr = JSON.parse(fs.readFileSync(process.argv[3]));

const metrics = ['largest-contentful-paint', 'cumulative-layout-shift', 'total-blocking-time'];

metrics.forEach(metric => {
  const base = baseline.audits[metric].numericValue;
  const current = pr.audits[metric].numericValue;
  const diff = ((current - base) / base * 100).toFixed(1);
  const emoji = current <= base ? '✅' : '❌';
  console.log(`${emoji} ${metric}: ${diff}% (${base.toFixed(0)} → ${current.toFixed(0)})`);
});

Troubleshooting

常见问题排查

IssueSolution
Inconsistent scoresRun multiple times (
--number-of-runs=3
), use median
Chrome not foundSet
CHROME_PATH
env var
TimeoutsIncrease with
--max-wait-for-load=60000
Auth requiredUse
--extra-headers
or puppeteer script
问题解决方案
评分不一致多次运行(使用
--number-of-runs=3
参数),取中位数
找不到Chrome设置
CHROME_PATH
环境变量
超时通过
--max-wait-for-load=60000
延长超时时间
需要身份验证使用
--extra-headers
参数或puppeteer脚本