Skip to content

Commit

Permalink
Add public getMetricRatingThresholds(), refactor MetricRatingThreshol…
Browse files Browse the repository at this point in the history
…ds type to be more self-explanatory
  • Loading branch information
robatron committed Mar 1, 2023
1 parent 25105d6 commit 94f05d9
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 116 deletions.
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,11 +768,16 @@ Metric-specific subclasses:
* The thresholds of metric's "good", "needs improvement", and "poor"
* ratings:
*
* - Metric values ≦ [0] are "good"
* - Metric values > [0] and ≦ [1] are "needs improvement"
* - Metric values > [1] are "poor".
* | Metric value | Rating |
* |-----------------------------------|---------------------|
* | ≦ `good` | "good" |
* | > `good` and ≦ `needsImprovement` | "needs improvement" |
* | > `needsImprovement` | "poor" |
*/
export type MetricRatingThresholds = [number, number];
export type MetricRatingThresholds = {
good: number;
needsImprovement: number;
};
```

#### `ReportCallback`
Expand Down Expand Up @@ -960,26 +965,21 @@ onTTFB((metric) => {
_**Note:** browsers that do not support `navigation` entries will fall back to
using `performance.timing` (with the timestamps converted from epoch time to [`DOMHighResTimeStamp`](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp)). This ensures code referencing these values (like in the example above) will work the same in all browsers._

### Rating Thresholds:
#### `getMetricRatingThresholds()`

Rating thresholds for each Web Vitals metric are available as [`MetricRatingThresholds`](/src/types/base.ts#:~:text=type%20MetricRatingThresholds):
```ts
type getMetricRatingThresholds = (
metricName: Metric['name']
) => MetricRatingThresholds | null;
```

`getMetricRatingThresholds()` can be used to get rating thresholds for each Web Vitals metric as [`MetricRatingThresholds`](/src/types/base.ts#:~:text=type%20MetricRatingThresholds). Example:

```ts
import {
CLSThresholds,
FCPThresholds,
FIDThresholds,
INPThresholds,
LCPThresholds,
TTFBThresholds,
} from 'web-vitals';

console.log(CLSThresholds); // [ 0.1, 0.25 ]
console.log(FCPThresholds); // [ 1800, 3000 ]
console.log(FIDThresholds); // [ 100, 300 ]
console.log(INPThresholds); // [ 200, 500 ]
console.log(LCPThresholds); // [ 2500, 4000 ]
console.log(TTFBThresholds); // [ 800, 1800 ]
import {getMetricRatingThresholds} from 'web-vitals';

console.log(getMetricRatingThresholds('CLS'));
// {good: 0.1, needsImprovement: 0.25}
```

### Attribution:
Expand Down
13 changes: 7 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
* limitations under the License.
*/

export {onCLS, CLSThresholds} from './onCLS.js';
export {onFCP, FCPThresholds} from './onFCP.js';
export {onFID, FIDThresholds} from './onFID.js';
export {onINP, INPThresholds} from './onINP.js';
export {onLCP, LCPThresholds} from './onLCP.js';
export {onTTFB, TTFBThresholds} from './onTTFB.js';
export {onCLS} from './onCLS.js';
export {onFCP} from './onFCP.js';
export {onFID} from './onFID.js';
export {onINP} from './onINP.js';
export {onLCP} from './onLCP.js';
export {onTTFB} from './onTTFB.js';
export {getMetricRatingThresholds} from './lib/getMetricRatingThresholds.js';

export * from './deprecated.js';
export * from './types.js';
8 changes: 4 additions & 4 deletions src/lib/bindReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const getRating = (
value: number,
thresholds: MetricRatingThresholds
): Metric['rating'] => {
if (value > thresholds[1]) {
if (value > thresholds.needsImprovement) {
return 'poor';
}
if (value > thresholds[0]) {
if (value > thresholds.good) {
return 'needs-improvement';
}
return 'good';
Expand All @@ -32,13 +32,13 @@ const getRating = (
export const bindReporter = (
callback: ReportCallback,
metric: Metric,
thresholds: MetricRatingThresholds,
thresholds: MetricRatingThresholds | null,
reportAllChanges?: boolean
) => {
let prevValue: number;
let delta: number;
return (forceReport?: boolean) => {
if (metric.value >= 0) {
if (metric.value >= 0 && thresholds !== null) {
if (forceReport || reportAllChanges) {
delta = metric.value - (prevValue || 0);

Expand Down
64 changes: 64 additions & 0 deletions src/lib/getMetricRatingThresholds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {Metric, MetricRatingThresholds} from '../types.js';

/** The defined rating thresholds of each metric */
const metricRatingThresholds: Record<Metric['name'], MetricRatingThresholds> = {
// https://web.dev/cls/#what-is-a-good-cls-score
'CLS': {
good: 0.1,
needsImprovement: 0.25,
},
// https://web.dev/fcp/#what-is-a-good-fcp-score
'FCP': {
good: 1800,
needsImprovement: 3000,
},
// https://web.dev/fid/#what-is-a-good-fid-score
'FID': {
good: 100,
needsImprovement: 300,
},
// https://web.dev/inp/#what-is-a-good-inp-score
'INP': {
good: 200,
needsImprovement: 500,
},
// https://web.dev/lcp/#what-is-a-good-lcp-score
'LCP': {
good: 2500,
needsImprovement: 4000,
},
// https://web.dev/ttfb/#what-is-a-good-ttfb-score
'TTFB': {
good: 800,
needsImprovement: 1800,
},
};

/**
* Get the thresholds of a metric's "good", "needs improvement", and "poor"
* ratings, formatted as `MetricRatingThresholds`:
*
* | Metric value | Rating |
* |-----------------------------------|---------------------|
* | ≦ `good` | "good" |
* | > `good` and ≦ `needsImprovement` | "needs improvement" |
* | > `needsImprovement` | "poor" |
*
* @Example
* ```ts
* getMetricRatingThresholds('CLS') → {good: 0.1, needsImprovement: 0.25}
* ```
* @returns The metric's rating thresholds or `null` if `metricName is invalid
*/
export const getMetricRatingThresholds = (
metricName: Metric['name']
): MetricRatingThresholds | null => {
try {
// Return a copy to prevent changes
const {good, needsImprovement} = metricRatingThresholds[metricName];
return {good, needsImprovement};
} catch (e) {
// Return null for invalid metric names
return null;
}
};
17 changes: 5 additions & 12 deletions src/onCLS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,8 @@ import {doubleRAF} from './lib/doubleRAF.js';
import {onHidden} from './lib/onHidden.js';
import {runOnce} from './lib/runOnce.js';
import {onFCP} from './onFCP.js';
import {
CLSMetric,
CLSReportCallback,
MetricRatingThresholds,
ReportOpts,
} from './types.js';

/** Thresholds for CLS. See https://web.dev/cls/#what-is-a-good-cls-score */
export const CLSThresholds: MetricRatingThresholds = [0.1, 0.25];
import {CLSMetric, CLSReportCallback, ReportOpts} from './types.js';
import {getMetricRatingThresholds} from './lib/getMetricRatingThresholds.js';

/**
* Calculates the [CLS](https://web.dev/cls/) value for the current page and
Expand Down Expand Up @@ -61,9 +54,9 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => {
// Note: this is done to match the current behavior of CrUX.
onFCP(
runOnce(() => {
const thresholds = getMetricRatingThresholds('CLS');
let metric = initMetric('CLS', 0);
let report: ReturnType<typeof bindReporter>;

let sessionValue = 0;
let sessionEntries: PerformanceEntry[] = [];

Expand Down Expand Up @@ -107,7 +100,7 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
CLSThresholds,
thresholds,
opts!.reportAllChanges
);

Expand All @@ -124,7 +117,7 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
CLSThresholds,
thresholds,
opts!.reportAllChanges
);

Expand Down
16 changes: 5 additions & 11 deletions src/onFCP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,12 @@ import {onBFCacheRestore} from './lib/bfcache.js';
import {bindReporter} from './lib/bindReporter.js';
import {doubleRAF} from './lib/doubleRAF.js';
import {getActivationStart} from './lib/getActivationStart.js';
import {getMetricRatingThresholds} from './lib/getMetricRatingThresholds.js';
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js';
import {initMetric} from './lib/initMetric.js';
import {observe} from './lib/observe.js';
import {whenActivated} from './lib/whenActivated.js';
import {
FCPMetric,
FCPReportCallback,
MetricRatingThresholds,
ReportOpts,
} from './types.js';

/** Thresholds for FCP. See https://web.dev/fcp/#what-is-a-good-fcp-score */
export const FCPThresholds: MetricRatingThresholds = [1800, 3000];
import {FCPMetric, FCPReportCallback, ReportOpts} from './types.js';

/**
* Calculates the [FCP](https://web.dev/fcp/) value for the current page and
Expand All @@ -43,6 +36,7 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => {
opts = opts || {};

whenActivated(() => {
const thresholds = getMetricRatingThresholds('FCP');
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('FCP');
let report: ReturnType<typeof bindReporter>;
Expand Down Expand Up @@ -72,7 +66,7 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
FCPThresholds,
thresholds,
opts!.reportAllChanges
);

Expand All @@ -83,7 +77,7 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
FCPThresholds,
thresholds,
opts!.reportAllChanges
);

Expand Down
17 changes: 5 additions & 12 deletions src/onFID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {onBFCacheRestore} from './lib/bfcache.js';
import {bindReporter} from './lib/bindReporter.js';
import {getMetricRatingThresholds} from './lib/getMetricRatingThresholds.js';
import {getVisibilityWatcher} from './lib/getVisibilityWatcher.js';
import {initMetric} from './lib/initMetric.js';
import {observe} from './lib/observe.js';
Expand All @@ -29,14 +30,10 @@ import {whenActivated} from './lib/whenActivated.js';
import {
FIDMetric,
FirstInputPolyfillCallback,
MetricRatingThresholds,
ReportCallback,
ReportOpts,
} from './types.js';

/** Thresholds for FID. See https://web.dev/fid/#what-is-a-good-fid-score */
export const FIDThresholds: MetricRatingThresholds = [100, 300];

/**
* Calculates the [FID](https://web.dev/fid/) value for the current page and
* calls the `callback` function once the value is ready, along with the
Expand All @@ -51,6 +48,7 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => {
opts = opts || {};

whenActivated(() => {
const thresholds = getMetricRatingThresholds('FID');
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('FID');
let report: ReturnType<typeof bindReporter>;
Expand All @@ -69,12 +67,7 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => {
};

const po = observe('first-input', handleEntries);
report = bindReporter(
onReport,
metric,
FIDThresholds,
opts!.reportAllChanges
);
report = bindReporter(onReport, metric, thresholds, opts!.reportAllChanges);

if (po) {
onHidden(
Expand All @@ -101,7 +94,7 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
FIDThresholds,
thresholds,
opts!.reportAllChanges
);

Expand All @@ -118,7 +111,7 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
FIDThresholds,
thresholds,
opts!.reportAllChanges
);

Expand Down
21 changes: 5 additions & 16 deletions src/onINP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {onBFCacheRestore} from './lib/bfcache.js';
import {bindReporter} from './lib/bindReporter.js';
import {getMetricRatingThresholds} from './lib/getMetricRatingThresholds.js';
import {initMetric} from './lib/initMetric.js';
import {observe} from './lib/observe.js';
import {onHidden} from './lib/onHidden.js';
Expand All @@ -24,22 +25,14 @@ import {
initInteractionCountPolyfill,
} from './lib/polyfills/interactionCountPolyfill.js';
import {whenActivated} from './lib/whenActivated.js';
import {
INPMetric,
MetricRatingThresholds,
ReportCallback,
ReportOpts,
} from './types.js';
import {INPMetric, ReportCallback, ReportOpts} from './types.js';

interface Interaction {
id: number;
latency: number;
entries: PerformanceEventTiming[];
}

/** Thresholds for INP. See https://web.dev/inp/#what-is-a-good-inp-score */
export const INPThresholds: MetricRatingThresholds = [200, 500];

// Used to store the interaction count after a bfcache restore, since p98
// interaction latencies should only consider the current navigation.
let prevInteractionCount = 0;
Expand Down Expand Up @@ -157,6 +150,7 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => {
// TODO(philipwalton): remove once the polyfill is no longer needed.
initInteractionCountPolyfill();

const thresholds = getMetricRatingThresholds('INP');
let metric = initMetric('INP');
let report: ReturnType<typeof bindReporter>;

Expand Down Expand Up @@ -210,12 +204,7 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => {
durationThreshold: opts!.durationThreshold || 40,
} as PerformanceObserverInit);

report = bindReporter(
onReport,
metric,
INPThresholds,
opts!.reportAllChanges
);
report = bindReporter(onReport, metric, thresholds, opts!.reportAllChanges);

if (po) {
// Also observe entries of type `first-input`. This is useful in cases
Expand Down Expand Up @@ -247,7 +236,7 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => {
report = bindReporter(
onReport,
metric,
INPThresholds,
thresholds,
opts!.reportAllChanges
);
});
Expand Down
Loading

0 comments on commit 94f05d9

Please sign in to comment.