main fc2a2b50194a cached
24 files
39.1 KB
10.7k tokens
25 symbols
1 requests
Download .txt
Repository: treosh/lighthouse-plugin-field-performance
Branch: main
Commit: fc2a2b50194a
Files: 24
Total size: 39.1 KB

Directory structure:
gitextract__0o96pqn/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package.json
├── results/
│   └── .gitkeep
├── src/
│   ├── audits/
│   │   ├── field-cls-origin.js
│   │   ├── field-cls.js
│   │   ├── field-fcp-origin.js
│   │   ├── field-fcp.js
│   │   ├── field-fid-origin.js
│   │   ├── field-fid.js
│   │   ├── field-lcp-origin.js
│   │   └── field-lcp.js
│   ├── index.js
│   └── utils/
│       ├── audit-helpers.js
│       └── run-psi.js
├── test/
│   ├── index.js
│   ├── snapshots/
│   │   └── test/
│   │       ├── index.js.md
│   │       └── index.js.snap
│   └── types.d.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use node 12
        uses: actions/setup-node@v2
        with:
          node-version: '12'
      - name: Install, link plugin locally, and test
        run: |
          yarn install
          yarn link
          yarn link lighthouse-plugin-field-performance
          yarn test


================================================
FILE: .gitignore
================================================
node_modules
results/*
!results/.gitkeep

================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "semi": false,
  "printWidth": 120
}


================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

First of all, thank you for your interest in `lighthouse-plugin-field-performance`!
We'd love to accept your patches and contributions!

#### 1. Install dependencies

```bash
yarn install
```

#### 2. Run plugin

```bash
yarn install # install deps
yarn link # create a global link for lighthouse-plugin-field-performance
yarn link lighthouse-plugin-field-performance # add symlink to test the plugin locally

yarn mobile-run https://www.apple.com/ # test plugin with a real PSI API response
yarn desktop-run https://www.booking.com/

yarn mobile-run https://treo.sh/ # empty response
yarn desktop-run https://treo.sh/ # just origin
```

`lighthouse-plugin-field-performance` folder is made of symlinks for a simple local testing.

#### 3. Improve the plugin

Write your patch. Improve the plugin to help capture Field Performance.

Helpful links:

- [Plugin docs](https://github.com/GoogleChrome/lighthouse/blob/master/docs/plugins.md)
- [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights)
- [PSI API](https://developers.google.com/speed/docs/insights/v5/get-started)

#### 4. Tests and linters

Coding style is fully defined in [.prettierrc](./.prettierrc).
We use [JSDoc](http://usejsdoc.org/) with [TypeScript](https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript) for linting and annotations.

```bash
# https://github.com/GoogleChrome/lighthouse/issues/9050#issuecomment-495678706
yarn link && yarn link lighthouse-plugin-field-performance # install plugin locally

yarn test # run all linters && tests
yarn tsc -p . # run typescript checks
yarn ava test/index.js # run just AVA tests
yarn ava test/index.js -u # update AVA snapshots
PSI_TOKEN=... yarn ava test/index.js # run AVA with PSI_TOKEN
```


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) Treo.sh <info@treo.sh> (https://treo.sh/)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# lighthouse-plugin-field-performance

> Lighthouse plugin that adds field data to your report. It uses real-user data from Chrome UX Report and Core Web Vitals logic to estimate the score.

[An example report for developers.google.com](https://googlechrome.github.io/lighthouse/viewer/?gist=d9072ab8ccb30622deab48e6d5ee229c):

<a href="https://googlechrome.github.io/lighthouse/viewer/?gist=d9072ab8ccb30622deab48e6d5ee229c">
  <img width="1162" alt="Lighthouse Field Performance Plugin" src="https://user-images.githubusercontent.com/158189/83353335-27499180-a352-11ea-8ee5-059582117a14.png">
</a>

<br />
<br />

This plugin adds Core Web Vitals values to your Lighthouse report. The Field Performance category includes real-user data provided by [Chrome UX Report](https://developers.google.com/web/tools/chrome-user-experience-report/). It's similar to the field section in [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/).

The scoring algorithm weighs values for Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS). It uses the Core Web Vitals assessment logic that sets 1 if metric is good, 0 if metric is poor, and a value from 0 to 1 if it's between. (_Note_: FCP and the origin values do not affect the score, [see the source](./src/index.js))

Check out the parity between Field & Lab performance on mobile:

<img width="973" alt="Field & lab performance on mobile" src="https://user-images.githubusercontent.com/158189/83353215-31b75b80-a351-11ea-801e-07f5a2b73e51.png">

And on desktop:

<img width="972" alt="Field & lab performance on desktop" src="https://user-images.githubusercontent.com/158189/83353212-2ebc6b00-a351-11ea-9cf8-6a04a5f0f903.png">

Sometimes field data is missing because a URL doesn't have enough anonymous traffic. In this case, the lab data is the only available measurement.

## Install

Requires Node.js `12+` and Lighthouse `8+`.

```bash
$ npm install lighthouse lighthouse-plugin-field-performance
```

## Usage

Use the plugin with [Lighthouse CLI](https://github.com/GoogleChrome/lighthouse):

```bash
$ npx lighthouse https://www.apple.com/ --plugins=lighthouse-plugin-field-performance
```

To run more requests, provide your [PageSpeed Insights token](https://developers.google.com/speed/docs/insights/v5/get-started) using a custom config:

```bash
$ npx lighthouse https://www.apple.com/ --config-path=./config.js
```

`config.js`

```js
module.exports = {
  extends: 'lighthouse:default',
  plugins: ['lighthouse-plugin-field-performance'],
  settings: {
    psiToken: 'YOUR_REAL_TOKEN',
  },
}
```

## Credits

Sponsored by [Treo.sh - Page speed monitoring made simple](https://treo.sh).

[![](https://github.com/treosh/lighthouse-plugin-field-performance/workflows/CI/badge.svg)](https://github.com/treosh/lighthouse-plugin-field-performance/actions?workflow=CI)
[![](https://img.shields.io/npm/v/lighthouse-plugin-field-performance.svg)](https://npmjs.org/package/lighthouse-plugin-field-performance)
[![](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)


================================================
FILE: package.json
================================================
{
  "name": "lighthouse-plugin-field-performance",
  "version": "3.0.0",
  "description": "Lighthouse plugin that shows real-user data (field data) from Chrome UX Report",
  "repository": "https://github.com/treosh/lighthouse-plugin-field-performance",
  "author": "Aleksey Kulikov <alekseykulikov@me.com>, Artem Denysov <denysov.artem@gmail.com>",
  "license": "MIT",
  "engines": {
    "node": ">=10"
  },
  "main": "src/index.js",
  "files": [
    "src"
  ],
  "keywords": [
    "lighthouse",
    "lighthouse plugin",
    "chrome user experience report",
    "crux",
    "chrome ux report",
    "real user monitoring",
    "first contentful paint",
    "first input delay"
  ],
  "scripts": {
    "test": "prettier -c src/** test/** package.json README.md && tsc -p . && ava",
    "mobile-run": "lighthouse --plugins=lighthouse-plugin-field-performance --view --chrome-flags='--headless' --quiet --only-categories=performance,lighthouse-plugin-field-performance --output-path=./results/mobile.html",
    "desktop-run": "lighthouse --plugins=lighthouse-plugin-field-performance --view --preset=desktop --chrome-flags='--headless' --quiet --only-categories=performance,lighthouse-plugin-field-performance --output-path=./results/desktop.html"
  },
  "ava": {
    "snapshotDir": "test/snapshots",
    "files": [
      "test/index.js"
    ],
    "timeout": "30s"
  },
  "dependencies": {
    "lodash": "^4.17.21",
    "node-fetch": "^2.6.1"
  },
  "peerDependencies": {
    "lighthouse": "8 - 11"
  },
  "devDependencies": {
    "@types/lodash": "^4.14.172",
    "@types/node": "16.7.10",
    "@types/node-fetch": "^2.5.12",
    "ava": "^3.15.0",
    "lighthouse": "^8.3.0",
    "prettier": "^2.3.2",
    "typescript": "^4.4.2"
  }
}


================================================
FILE: results/.gitkeep
================================================


================================================
FILE: src/audits/field-cls-origin.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldClsOriginAudit extends Audit {
  static get meta() {
    return {
      id: 'field-cls-origin',
      title: 'Cumulative Layout Shift (Origin)',
      description:
        'Cumulative Layout Shift (CLS) measures visual stability, and it helps quantify how often users experience unexpected layout shifts. The value is 75th percentile of the origin traffic. [Learn more about CLS](https://web.dev/cls/)',
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const ole = await getLoadingExperience(artifacts, context, false)
      if (!isResultsInField(ole)) return createNotApplicableResult(FieldClsOriginAudit.meta.title)
      return createValueResult(ole.metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE, 'cls')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-cls.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldClsAudit extends Audit {
  static get meta() {
    return {
      id: 'field-cls',
      title: 'Cumulative Layout Shift (URL)',
      description: `Cumulative Layout Shift (CLS) measures the sum of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A low CLS (75th percentile) helps ensure that the page is delightful. [Learn more about CLS](https://web.dev/cls/)`,
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const le = await getLoadingExperience(artifacts, context)
      if (!isResultsInField(le)) return createNotApplicableResult(FieldClsAudit.meta.title)
      return createValueResult(le.metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE, 'cls')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-fcp-origin.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldFcpOriginAudit extends Audit {
  static get meta() {
    return {
      id: 'field-fcp-origin',
      title: 'First Contentful Paint (Origin)',
      description:
        'First Contentful Paint (FCP) marks the first time in the page load timeline where the user can see anything on the screen. The value is 75th percentile of the origin traffic. [Learn more about FCP](https://web.dev/fcp/)',
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const ole = await getLoadingExperience(artifacts, context, false)
      if (!isResultsInField(ole)) return createNotApplicableResult(FieldFcpOriginAudit.meta.title)
      return createValueResult(ole.metrics.FIRST_CONTENTFUL_PAINT_MS, 'fcp')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-fcp.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldFcpAudit extends Audit {
  static get meta() {
    return {
      id: 'field-fcp',
      title: 'First Contentful Paint (URL)',
      description:
        'First Contentful Paint (FCP) marks the first time in the page load timeline where the user can see anything on the screen. A fast FCP (75th percentile) helps reassure the user that something is happening. [Learn more about FCP](https://web.dev/fcp/)',
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const le = await getLoadingExperience(artifacts, context)
      if (!isResultsInField(le)) return createNotApplicableResult(FieldFcpAudit.meta.title)
      return createValueResult(le.metrics.FIRST_CONTENTFUL_PAINT_MS, 'fcp')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-fid-origin.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldFidOriginAudit extends Audit {
  static get meta() {
    return {
      id: 'field-fid-origin',
      title: 'First Input Delay (Origin)',
      description: `First Input Delay (FID) quantifies the experience users feel when trying to interact with unresponsive pages. The value is 75th percentile of the origin traffic. [Learn more about FID](https://web.dev/fid/)`,
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const ole = await getLoadingExperience(artifacts, context, false)
      if (!isResultsInField(ole)) return createNotApplicableResult(FieldFidOriginAudit.meta.title)
      return createValueResult(ole.metrics.FIRST_INPUT_DELAY_MS, 'fid')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-fid.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldFidAudit extends Audit {
  static get meta() {
    return {
      id: 'field-fid',
      title: 'First Input Delay (URL)',
      description:
        'First Input Delay (FID) quantifies the experience users feel when trying to interact with unresponsive pages. A fast FID (75th percentile) helps ensure that the page is usable. [Learn more about FID](https://web.dev/fid/)',
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const le = await getLoadingExperience(artifacts, context)
      if (!isResultsInField(le)) return createNotApplicableResult(FieldFidAudit.meta.title)
      return createValueResult(le.metrics.FIRST_INPUT_DELAY_MS, 'fid')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-lcp-origin.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldLcpOriginAudit extends Audit {
  static get meta() {
    return {
      id: 'field-lcp-origin',
      title: 'Largest Contentful Paint (Origin)',
      description: `Largest Contentful Paint (LCP) marks the time in the page load timeline when the page's main content has likely loaded. The value is 75th percentile of the origin traffic. [Learn more about LCP](https://web.dev/lcp/)`,
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const ole = await getLoadingExperience(artifacts, context, false)
      if (!isResultsInField(ole)) return createNotApplicableResult(FieldLcpOriginAudit.meta.title)
      return createValueResult(ole.metrics.LARGEST_CONTENTFUL_PAINT_MS, 'lcp')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/audits/field-lcp.js
================================================
const { Audit } = require('lighthouse')
const {
  getLoadingExperience,
  createNotApplicableResult,
  createValueResult,
  createErrorResult,
  isResultsInField,
} = require('../utils/audit-helpers')

module.exports = class FieldLcpAudit extends Audit {
  static get meta() {
    return {
      id: 'field-lcp',
      title: 'Largest Contentful Paint (URL)',
      description: `Largest Contentful Paint (LCP) reports the render time of the largest content element that is visible within the viewport. A fast LCP (75th percentile) helps reassure the user that the page is useful. [Learn more about LCP](https://web.dev/lcp/)`,
      scoreDisplayMode: 'numeric',
      requiredArtifacts: ['URL', 'settings'],
    }
  }

  /** @param {Object} artifacts @param {Object} context */
  static async audit(artifacts, context) {
    try {
      const le = await getLoadingExperience(artifacts, context)
      if (!isResultsInField(le)) return createNotApplicableResult(FieldLcpAudit.meta.title)
      return createValueResult(le.metrics.LARGEST_CONTENTFUL_PAINT_MS, 'lcp')
    } catch (err) {
      return createErrorResult(err)
    }
  }
}


================================================
FILE: src/index.js
================================================
module.exports = {
  audits: [
    { path: 'lighthouse-plugin-field-performance/src/audits/field-fcp.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-lcp.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-fid.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-cls.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-fcp-origin.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-lcp-origin.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-fid-origin.js' },
    { path: 'lighthouse-plugin-field-performance/src/audits/field-cls-origin.js' },
  ],
  groups: {
    page: {
      title: 'Page summary',
    },
    origin: {
      title: 'Origin summary',
    },
  },
  category: {
    title: 'Field Performance',
    description:
      'These metrics show the performance of the page over the past 30 days. Data is collected anonymously in for real-world Chrome users and provided by Chrome UX Report. [Learn More](https://developers.google.com/web/tools/chrome-user-experience-report/)',
    auditRefs: [
      // CWVs are weighted equally
      { id: 'field-fcp', weight: 0, group: 'page' },
      { id: 'field-lcp', weight: 1, group: 'page' },
      { id: 'field-fid', weight: 1, group: 'page' },
      { id: 'field-cls', weight: 1, group: 'page' },
      { id: 'field-fcp-origin', weight: 0, group: 'origin' },
      { id: 'field-lcp-origin', weight: 0, group: 'origin' },
      { id: 'field-fid-origin', weight: 0, group: 'origin' },
      { id: 'field-cls-origin', weight: 0, group: 'origin' },
    ],
  },
}


================================================
FILE: src/utils/audit-helpers.js
================================================
const { round } = require('lodash')
const { Audit } = require('lighthouse')
const { runPsi } = require('./run-psi')

/**
 * @typedef {{ good: number, poor: number }} Range
 * @typedef {'fcp' | 'lcp' | 'fid' | 'cls'} Metric
 * @typedef {{ percentile: number, distributions: { min: number, max: number, proportion: number}[] }} MetricValue
 * @typedef {{ id: string, overall_category: string, initial_url: string
               metrics: { FIRST_INPUT_DELAY_MS: MetricValue, FIRST_CONTENTFUL_PAINT_MS: MetricValue, LARGEST_CONTENTFUL_PAINT_MS: MetricValue, CUMULATIVE_LAYOUT_SHIFT_SCORE: MetricValue } }} LoadingExperience
 */

// cache PSI requests

const requests = new Map()

/**
 * Cache results and parse crux data.
 *
 * @param {any} artifacts
 * @param {any} context
 * @param {boolean} [isUrl]
 * @return {Promise<LoadingExperience>}
 */

exports.getLoadingExperience = async (artifacts, context, isUrl = true) => {
  const psiToken = context.settings.psiToken || null
  const strategy = artifacts.settings.formFactor === 'desktop' ? 'desktop' : 'mobile'
  const prefix = isUrl ? 'url' : 'origin'
  const { href, origin } = new URL(artifacts.URL.finalUrl)
  const url = `${prefix}:${href}`
  const key = url + strategy
  if (!requests.has(key)) {
    requests.set(key, runPsi({ url, strategy, psiToken }))
  }
  const json = await requests.get(key)
  if (json.error) throw new Error(JSON.stringify(json.error))
  // check, that URL response is not for origin
  if (isUrl) {
    const hasUrlExperience = json.loadingExperience && json.loadingExperience.id !== origin
    return hasUrlExperience ? json.loadingExperience : null
  }
  return json.loadingExperience
}

/**
 * Estimate value and create numeric results
 *
 * @param {MetricValue} metricValue
 * @param {Metric} metric
 * @return {Object}
 */

exports.createValueResult = (metricValue, metric) => {
  const numericValue = normalizeMetricValue(metric, metricValue.percentile)
  return {
    numericValue,
    score: estimateMetricScore(getMetricRange(metric), numericValue),
    numericUnit: getMetricNumericUnit(metric),
    displayValue: formatMetric(metric, numericValue),
    details: createDistributionsTable(metricValue, metric),
  }
}

/**
 * Create result when data does not exist.
 *
 * @param {string} title
 */

exports.createNotApplicableResult = (title) => {
  return {
    score: null,
    notApplicable: true,
    explanation: `The Chrome User Experience Report 
          does not have sufficient real-world ${title} data for this page.`,
  }
}

/**
 * Create error result.
 *
 * @param {Error} err
 */

exports.createErrorResult = (err) => {
  console.log(err)
  return {
    score: null,
    errorMessage: err.toString(),
  }
}

/**
 * Checks if loading experience exists in field
 *
 * @param {LoadingExperience} le
 */

exports.isResultsInField = (le) => {
  return !!le && Boolean(Object.values(le.metrics || {}).length)
}

/**
 * @param {MetricValue} metricValue
 * @param {Metric} metric
 */

function createDistributionsTable({ distributions }, metric) {
  const headings = [
    { key: 'category', itemType: 'text', text: 'Category' },
    { key: 'distribution', itemType: 'text', text: 'Percent of traffic' },
  ]
  const items = distributions.map(({ min, max, proportion }, index) => {
    const item = {}
    const normMin = formatMetric(metric, normalizeMetricValue(metric, min))
    const normMax = formatMetric(metric, normalizeMetricValue(metric, max))

    if (min === 0) {
      item.category = `Good (faster than ${normMax})`
    } else if (max && min === distributions[index - 1].max) {
      item.category = `Needs improvement (from ${normMin} to ${normMax})`
    } else {
      item.category = `Poor (longer than ${normMin})`
    }

    item.distribution = `${round(proportion * 100, 1)} %`

    return item
  })

  return Audit.makeTableDetails(headings, items)
}

/**
 * Recommended ranks (https://web.dev/metrics/):
 *
 * FCP: Fast < 1.0 s,   Slow > 3.0 s
 * LCP: Fast < 2.5 s,   Slow > 4.0 s
 * FID: Fast < 100 ms,  Slow > 300 ms
 * CLS: Fast < 0.10,    Slow > 0.25
 *
 * @param {Metric} metric
 * @return {Range}
 */

function getMetricRange(metric) {
  switch (metric) {
    case 'fcp':
      return { good: 1000, poor: 3000 }
    case 'lcp':
      return { good: 2500, poor: 4000 }
    case 'fid':
      return { good: 100, poor: 150 }
    case 'cls':
      return { good: 0.1, poor: 0.25 }
    default:
      throw new Error(`Invalid metric range: ${metric}`)
  }
}

/**
 * Based on a precise drawing:
 * https://twitter.com/JohnMu/status/1395798952570724352
 *
 * @param {Range} range
 * @param {number} value
 */

function estimateMetricScore({ good, poor }, value) {
  if (value <= good) return 1
  if (value > poor) return 0
  const linearScore = round((poor - value) / (poor - good), 2)
  return linearScore
}

/** @param {Metric} metric, @param {number} value */
function formatMetric(metric, value) {
  switch (metric) {
    case 'fcp':
    case 'lcp':
      return round(value / 1000, 1).toFixed(1) + ' s'
    case 'fid':
      return round(round(value / 10) * 10) + ' ms'
    case 'cls':
      return value === 0 ? '0' : value === 0.1 ? '0.10' : round(value, 3).toString()
    default:
      throw new Error(`Invalid metric format: ${metric}`)
  }
}

/** @param {Metric} metric @param {number} value */
function normalizeMetricValue(metric, value) {
  return metric === 'cls' ? value / 100 : value
}

/** @param {Metric} metric */
function getMetricNumericUnit(metric) {
  return metric === 'cls' ? 'unitless' : 'millisecond'
}


================================================
FILE: src/utils/run-psi.js
================================================
const { default: fetch } = require('node-fetch')
const { stringify } = require('querystring')

// config

const runPagespeedUrl = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'
const retryDelay = 3000
const maxRetries = 3

// expose

module.exports = { runPsi }

/**
 * Run PSI API.
 *
 * `url:` or `origin:` prefixes uses to get data quickly.
 * https://github.com/GoogleChrome/lighthouse/issues/1453#issuecomment-530163997
 * https://developers.google.com/speed/docs/insights/v5/reference/pagespeedapi/runpagespeed
 *
 * @param {{url: string, strategy: string, category?: string, psiToken?: string}} opts
 * @param {number} [retryCounter]
 * @return {Promise<any>}
 */

async function runPsi(opts, retryCounter = 0) {
  const { url, category, strategy, psiToken } = opts
  const params = { url, strategy, ...(category ? { category } : {}), ...(psiToken ? { key: psiToken } : {}) }
  const strParams = stringify(params)
  const res = await fetch(runPagespeedUrl + '?' + strParams)
  if (res.status === 200) return res.json()

  const isJson = res.headers ? (res.headers.get('content-type') || '').includes('application/json') : false
  if (!isJson) {
    const text = await res.text()
    console.error('invalid PSI API response: status=%s text=%s', res.status, text)
    return retry()
  } else {
    const { error } = /** @type {any} */ (await res.json())
    console.error('invalid PSI API response: status=%s error=%j', res.status, error)
    if (error.code === 429) {
      console.log('error (%s): Too Many Requests', error.code)
      return retry()
    } else if (error.code === 400 || error.code === 500) {
      return { error }
    } else {
      throw new Error(`unknown response (${res.status}): ${error.message}`)
    }
  }

  /**
   * Retry PSI execution.
   *
   * @return {Promise<Object>}
   */

  async function retry() {
    if (retryCounter >= maxRetries) throw new Error(`maximum retries reached: ${retryCounter}`)
    console.log('wait %sms and retry', retryDelay)
    await new Promise((resolve) => setTimeout(resolve, retryDelay))
    return runPsi(opts, retryCounter + 1)
  }
}


================================================
FILE: test/index.js
================================================
const { serial } = require('ava')
const { omit, isNumber, isString } = require('lodash')
const chromeLauncher = require('chrome-launcher')
const lighthouse = require('lighthouse')
const psiToken = process.env.PSI_TOKEN || ''

serial.only('Measure field perf for site in CruX', async (t) => {
  const { audits, categories } = await runLighthouse('https://example.com/')
  const category = categories['lighthouse-plugin-field-performance']
  checkResponse('field-fcp')
  checkResponse('field-lcp')
  checkResponse('field-fid')
  checkResponse('field-cls')
  checkResponse('field-fcp-origin')
  checkResponse('field-lcp-origin')
  checkResponse('field-fid-origin')
  checkResponse('field-cls-origin')

  console.log('field performance score: %s', category.score)
  t.snapshot(omit(category, ['score']))

  /** @param {string} auditName */
  function checkResponse(auditName) {
    const audit = audits[auditName]
    t.snapshot(omit(audit, ['details', 'displayValue', 'numericValue', 'score']), `check ${auditName}`)
    t.true(isNumber(audit.score) && audit.score >= 0 && audit.score <= 1)
    t.true(isNumber(audit.numericValue))
    t.true(isString(audit.displayValue))
    console.log('%s: %s/%s – %s', auditName, audit.numericValue, audit.displayValue, audit.score)
    t.snapshot(
      {
        ...audit.details,
        items: audit.details.items.map(/** @param {object} item */ (item) => omit(item, ['distribution'])),
      },
      `details ${auditName}`
    )
  }
})

serial('Measure field perf for site site not in CruX', async (t) => {
  const { audits, categories } = await runLighthouse('https://alekseykulikov.com/')
  t.snapshot(audits['field-fcp'])
  t.snapshot(audits['field-lcp'])
  t.snapshot(audits['field-fid'])
  t.snapshot(audits['field-cls'])
  t.snapshot(audits['field-fcp-origin'])
  t.snapshot(audits['field-lcp-origin'])
  t.snapshot(audits['field-fid-origin'])
  t.snapshot(audits['field-cls-origin'])
  t.snapshot(categories['lighthouse-plugin-field-performance'])
})

/** @param {string} url */
async function runLighthouse(url) {
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless', '--enable-logging', '--no-sandbox'] })
  const flags = {
    port: chrome.port,
    onlyCategories: ['lighthouse-plugin-field-performance'],
  }
  const config = {
    extends: 'lighthouse:default',
    plugins: ['lighthouse-plugin-field-performance'],
    settings: psiToken ? { psiToken } : {},
  }
  const res = await lighthouse(url, flags, config)
  await chrome.kill()
  return res.lhr
}


================================================
FILE: test/snapshots/test/index.js.md
================================================
# Snapshot report for `test/index.js`

The actual snapshot is saved in `index.js.snap`.

Generated by [AVA](https://avajs.dev).

## Measure field perf for site in CruX

> check field-fcp

    {
      description: 'First Contentful Paint (FCP) marks the first time in the page load timeline where the user can see anything on the screen. A fast FCP (75th percentile) helps reassure the user that something is happening. [Learn more about FCP](https://web.dev/fcp/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-fcp',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'First Contentful Paint (URL)',
      warnings: undefined,
    }

> details field-fcp

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 1.8 s)',
        },
        {
          category: 'Needs improvement (from 1.8 s to 3.0 s)',
        },
        {
          category: 'Poor (longer than 3.0 s)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-lcp

    {
      description: 'Largest Contentful Paint (LCP) reports the render time of the largest content element that is visible within the viewport. A fast LCP (75th percentile) helps reassure the user that the page is useful. [Learn more about LCP](https://web.dev/lcp/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-lcp',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'Largest Contentful Paint (URL)',
      warnings: undefined,
    }

> details field-lcp

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 2.5 s)',
        },
        {
          category: 'Needs improvement (from 2.5 s to 4.0 s)',
        },
        {
          category: 'Poor (longer than 4.0 s)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-fid

    {
      description: 'First Input Delay (FID) quantifies the experience users feel when trying to interact with unresponsive pages. A fast FID (75th percentile) helps ensure that the page is usable. [Learn more about FID](https://web.dev/fid/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-fid',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'First Input Delay (URL)',
      warnings: undefined,
    }

> details field-fid

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 100 ms)',
        },
        {
          category: 'Needs improvement (from 100 ms to 300 ms)',
        },
        {
          category: 'Poor (longer than 300 ms)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-cls

    {
      description: 'Cumulative Layout Shift (CLS) measures the sum of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A low CLS (75th percentile) helps ensure that the page is delightful. [Learn more about CLS](https://web.dev/cls/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-cls',
      numericUnit: 'unitless',
      scoreDisplayMode: 'numeric',
      title: 'Cumulative Layout Shift (URL)',
      warnings: undefined,
    }

> details field-cls

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 0.10)',
        },
        {
          category: 'Needs improvement (from 0.10 to 0.25)',
        },
        {
          category: 'Poor (longer than 0.25)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-fcp-origin

    {
      description: 'First Contentful Paint (FCP) marks the first time in the page load timeline where the user can see anything on the screen. The value is 75th percentile of the origin traffic. [Learn more about FCP](https://web.dev/fcp/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-fcp-origin',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'First Contentful Paint (Origin)',
      warnings: undefined,
    }

> details field-fcp-origin

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 1.8 s)',
        },
        {
          category: 'Needs improvement (from 1.8 s to 3.0 s)',
        },
        {
          category: 'Poor (longer than 3.0 s)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-lcp-origin

    {
      description: 'Largest Contentful Paint (LCP) marks the time in the page load timeline when the page\'s main content has likely loaded. The value is 75th percentile of the origin traffic. [Learn more about LCP](https://web.dev/lcp/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-lcp-origin',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'Largest Contentful Paint (Origin)',
      warnings: undefined,
    }

> details field-lcp-origin

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 2.5 s)',
        },
        {
          category: 'Needs improvement (from 2.5 s to 4.0 s)',
        },
        {
          category: 'Poor (longer than 4.0 s)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-fid-origin

    {
      description: 'First Input Delay (FID) quantifies the experience users feel when trying to interact with unresponsive pages. The value is 75th percentile of the origin traffic. [Learn more about FID](https://web.dev/fid/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-fid-origin',
      numericUnit: 'millisecond',
      scoreDisplayMode: 'numeric',
      title: 'First Input Delay (Origin)',
      warnings: undefined,
    }

> details field-fid-origin

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 100 ms)',
        },
        {
          category: 'Needs improvement (from 100 ms to 300 ms)',
        },
        {
          category: 'Poor (longer than 300 ms)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> check field-cls-origin

    {
      description: 'Cumulative Layout Shift (CLS) measures visual stability, and it helps quantify how often users experience unexpected layout shifts. The value is 75th percentile of the origin traffic. [Learn more about CLS](https://web.dev/cls/)',
      errorMessage: undefined,
      explanation: undefined,
      id: 'field-cls-origin',
      numericUnit: 'unitless',
      scoreDisplayMode: 'numeric',
      title: 'Cumulative Layout Shift (Origin)',
      warnings: undefined,
    }

> details field-cls-origin

    {
      headings: [
        {
          itemType: 'text',
          key: 'category',
          text: 'Category',
        },
        {
          itemType: 'text',
          key: 'distribution',
          text: 'Percent of traffic',
        },
      ],
      items: [
        {
          category: 'Good (faster than 0.10)',
        },
        {
          category: 'Needs improvement (from 0.10 to 0.25)',
        },
        {
          category: 'Poor (longer than 0.25)',
        },
      ],
      summary: undefined,
      type: 'table',
    }

> Snapshot 17

    {
      auditRefs: [
        {
          group: 'lighthouse-plugin-field-performance-page',
          id: 'field-fcp',
          weight: 0,
        },
        {
          group: 'lighthouse-plugin-field-performance-page',
          id: 'field-lcp',
          weight: 1,
        },
        {
          group: 'lighthouse-plugin-field-performance-page',
          id: 'field-fid',
          weight: 1,
        },
        {
          group: 'lighthouse-plugin-field-performance-page',
          id: 'field-cls',
          weight: 1,
        },
        {
          group: 'lighthouse-plugin-field-performance-origin',
          id: 'field-fcp-origin',
          weight: 0,
        },
        {
          group: 'lighthouse-plugin-field-performance-origin',
          id: 'field-lcp-origin',
          weight: 0,
        },
        {
          group: 'lighthouse-plugin-field-performance-origin',
          id: 'field-fid-origin',
          weight: 0,
        },
        {
          group: 'lighthouse-plugin-field-performance-origin',
          id: 'field-cls-origin',
          weight: 0,
        },
      ],
      description: 'These metrics show the performance of the page over the past 30 days. Data is collected anonymously in for real-world Chrome users and provided by Chrome UX Report. [Learn More](https://developers.google.com/web/tools/chrome-user-experience-report/)',
      id: 'lighthouse-plugin-field-performance',
      title: 'Field Performance',
    }


================================================
FILE: test/types.d.ts
================================================
declare module 'lighthouse'
declare module 'lighthouse/lighthouse-core/scoring'


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "noEmit": true,
    "moduleResolution": "node",
    "module": "commonjs",
    "target": "es2018",
    "allowJs": true,
    "checkJs": true,
    "strict": true,
    "strictNullChecks": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "useUnknownInCatchVariables": false
  },
  "include": ["src", "test"]
}
Download .txt
gitextract__0o96pqn/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package.json
├── results/
│   └── .gitkeep
├── src/
│   ├── audits/
│   │   ├── field-cls-origin.js
│   │   ├── field-cls.js
│   │   ├── field-fcp-origin.js
│   │   ├── field-fcp.js
│   │   ├── field-fid-origin.js
│   │   ├── field-fid.js
│   │   ├── field-lcp-origin.js
│   │   └── field-lcp.js
│   ├── index.js
│   └── utils/
│       ├── audit-helpers.js
│       └── run-psi.js
├── test/
│   ├── index.js
│   ├── snapshots/
│   │   └── test/
│   │       ├── index.js.md
│   │       └── index.js.snap
│   └── types.d.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (25 symbols across 11 files)

FILE: src/audits/field-cls-origin.js
  method meta (line 11) | static get meta() {
  method audit (line 23) | static async audit(artifacts, context) {

FILE: src/audits/field-cls.js
  method meta (line 11) | static get meta() {
  method audit (line 22) | static async audit(artifacts, context) {

FILE: src/audits/field-fcp-origin.js
  method meta (line 11) | static get meta() {
  method audit (line 23) | static async audit(artifacts, context) {

FILE: src/audits/field-fcp.js
  method meta (line 11) | static get meta() {
  method audit (line 23) | static async audit(artifacts, context) {

FILE: src/audits/field-fid-origin.js
  method meta (line 11) | static get meta() {
  method audit (line 22) | static async audit(artifacts, context) {

FILE: src/audits/field-fid.js
  method meta (line 11) | static get meta() {
  method audit (line 23) | static async audit(artifacts, context) {

FILE: src/audits/field-lcp-origin.js
  method meta (line 11) | static get meta() {
  method audit (line 22) | static async audit(artifacts, context) {

FILE: src/audits/field-lcp.js
  method meta (line 11) | static get meta() {
  method audit (line 22) | static async audit(artifacts, context) {

FILE: src/utils/audit-helpers.js
  function createDistributionsTable (line 109) | function createDistributionsTable({ distributions }, metric) {
  function getMetricRange (line 147) | function getMetricRange(metric) {
  function estimateMetricScore (line 170) | function estimateMetricScore({ good, poor }, value) {
  function formatMetric (line 178) | function formatMetric(metric, value) {
  function normalizeMetricValue (line 193) | function normalizeMetricValue(metric, value) {
  function getMetricNumericUnit (line 198) | function getMetricNumericUnit(metric) {

FILE: src/utils/run-psi.js
  function runPsi (line 26) | async function runPsi(opts, retryCounter = 0) {

FILE: test/index.js
  function checkResponse (line 23) | function checkResponse(auditName) {
  function runLighthouse (line 54) | async function runLighthouse(url) {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 397,
    "preview": "name: CI\non: push\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: U"
  },
  {
    "path": ".gitignore",
    "chars": 40,
    "preview": "node_modules\nresults/*\n!results/.gitkeep"
  },
  {
    "path": ".prettierrc",
    "chars": 64,
    "preview": "{\n  \"singleQuote\": true,\n  \"semi\": false,\n  \"printWidth\": 120\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1772,
    "preview": "# How to Contribute\n\nFirst of all, thank you for your interest in `lighthouse-plugin-field-performance`!\nWe'd love to ac"
  },
  {
    "path": "LICENSE",
    "chars": 1103,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Treo.sh <info@treo.sh> (https://treo.sh/)\n\nPermission is hereby granted, free of ch"
  },
  {
    "path": "README.md",
    "chars": 3091,
    "preview": "# lighthouse-plugin-field-performance\n\n> Lighthouse plugin that adds field data to your report. It uses real-user data f"
  },
  {
    "path": "package.json",
    "chars": 1733,
    "preview": "{\n  \"name\": \"lighthouse-plugin-field-performance\",\n  \"version\": \"3.0.0\",\n  \"description\": \"Lighthouse plugin that shows "
  },
  {
    "path": "results/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/audits/field-cls-origin.js",
    "chars": 1158,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-cls.js",
    "chars": 1175,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-fcp-origin.js",
    "chars": 1144,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-fcp.js",
    "chars": 1142,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-fid-origin.js",
    "chars": 1113,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-fid.js",
    "chars": 1104,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-lcp-origin.js",
    "chars": 1137,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/audits/field-lcp.js",
    "chars": 1134,
    "preview": "const { Audit } = require('lighthouse')\nconst {\n  getLoadingExperience,\n  createNotApplicableResult,\n  createValueResult"
  },
  {
    "path": "src/index.js",
    "chars": 1645,
    "preview": "module.exports = {\n  audits: [\n    { path: 'lighthouse-plugin-field-performance/src/audits/field-fcp.js' },\n    { path: "
  },
  {
    "path": "src/utils/audit-helpers.js",
    "chars": 5547,
    "preview": "const { round } = require('lodash')\nconst { Audit } = require('lighthouse')\nconst { runPsi } = require('./run-psi')\n\n/**"
  },
  {
    "path": "src/utils/run-psi.js",
    "chars": 2119,
    "preview": "const { default: fetch } = require('node-fetch')\nconst { stringify } = require('querystring')\n\n// config\n\nconst runPages"
  },
  {
    "path": "test/index.js",
    "chars": 2531,
    "preview": "const { serial } = require('ava')\nconst { omit, isNumber, isString } = require('lodash')\nconst chromeLauncher = require("
  },
  {
    "path": "test/snapshots/test/index.js.md",
    "chars": 10459,
    "preview": "# Snapshot report for `test/index.js`\n\nThe actual snapshot is saved in `index.js.snap`.\n\nGenerated by [AVA](https://avaj"
  },
  {
    "path": "test/types.d.ts",
    "chars": 80,
    "preview": "declare module 'lighthouse'\ndeclare module 'lighthouse/lighthouse-core/scoring'\n"
  },
  {
    "path": "tsconfig.json",
    "chars": 386,
    "preview": "{\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"moduleResolution\": \"node\",\n    \"module\": \"commonjs\",\n    \"target\": \"es"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the treosh/lighthouse-plugin-field-performance GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (39.1 KB), approximately 10.7k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!