Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds and exports resetINP to reset INP metric #1

Merged
merged 4 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ For details on the difference between the builds, see <a href="#which-build-is-r
To load the "standard" build, import modules from the `web-vitals` package in your application code (as you would with any npm package and node-based build tool):

```js
import {onLCP, onFID, onCLS} from 'web-vitals';
import {onLCP, onFID, onCLS} from '@descript/web-vitals';

onCLS(console.log);
onFID(console.log);
Expand All @@ -97,8 +97,8 @@ The "attribution" build is slightly larger than the "standard" build (by about 6
To load the "attribution" build, change any `import` statements that reference `web-vitals` to `web-vitals/attribution`:

```diff
- import {onLCP, onFID, onCLS} from 'web-vitals';
+ import {onLCP, onFID, onCLS} from 'web-vitals/attribution';
- import {onLCP, onFID, onCLS} from '@descript/web-vitals';
+ import {onLCP, onFID, onCLS} from '@descript/web-vitals/attribution';
```

Usage for each of the imported function is identical to the standard build, but when importing from the attribution build, the [`Metric`](#metric) object will contain an additional [`attribution`](#metricwithattribution) property.
Expand All @@ -116,8 +116,8 @@ Loading the "base+polyfill" build is a two-step process:
First, in your application code, import the "base" build rather than the "standard" build. To do this, change any `import` statements that reference `web-vitals` to `web-vitals/base`:

```diff
- import {onLCP, onFID, onCLS} from 'web-vitals';
+ import {onLCP, onFID, onCLS} from 'web-vitals/base';
- import {onLCP, onFID, onCLS} from '@descript/web-vitals';
+ import {onLCP, onFID, onCLS} from '@descript/web-vitals/base';
```

Then, inline the code from `dist/polyfill.js` into the `<head>` of your pages. This step is important since the "base" build will error if the polyfill code has not been added.
Expand Down Expand Up @@ -242,7 +242,7 @@ The following example measures each of the Core Web Vitals metrics and logs the
_(The examples below import the "standard" build, but they will work with the "attribution" build as well.)_

```js
import {onCLS, onFID, onLCP} from 'web-vitals';
import {onCLS, onFID, onLCP} from '@descript/web-vitals';

onCLS(console.log);
onFID(console.log);
Expand Down Expand Up @@ -272,7 +272,7 @@ _**Important:** `reportAllChanges` only reports when the **metric changes**, not
This can be useful when debugging, but in general using `reportAllChanges` is not needed (or recommended) for measuring these metrics in production.

```js
import {onCLS} from 'web-vitals';
import {onCLS} from '@descript/web-vitals';

// Logs CLS as the value changes.
onCLS(console.log, {reportAllChanges: true});
Expand All @@ -287,7 +287,7 @@ Other analytics providers, however, do not allow this, so instead of reporting t
The following example shows how to use the `id` and `delta` properties:

```js
import {onCLS, onFID, onLCP} from 'web-vitals';
import {onCLS, onFID, onLCP} from '@descript/web-vitals';

function logDelta({name, id, delta}) {
console.log(`${name} matching ID ${id} changed by ${delta}`);
Expand All @@ -309,7 +309,7 @@ The following example measures each of the Core Web Vitals metrics and reports t
The `sendToAnalytics()` function uses the [`navigator.sendBeacon()`](https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon) method (if available), but falls back to the [`fetch()`](https://developer.mozilla.org/docs/Web/API/Fetch_API) API when not.

```js
import {onCLS, onFID, onLCP} from 'web-vitals';
import {onCLS, onFID, onLCP} from '@descript/web-vitals';

function sendToAnalytics(metric) {
// Replace with whatever serialization method you prefer.
Expand All @@ -333,7 +333,7 @@ Google Analytics does not support reporting metric distributions in any of its b
[Google Analytics 4](https://support.google.com/analytics/answer/10089681) introduces a new Event model allowing custom parameters instead of a fixed category, action, and label. It also supports non-integer values, making it easier to measure Web Vitals metrics compared to previous versions.

```js
import {onCLS, onFID, onLCP} from 'web-vitals';
import {onCLS, onFID, onLCP} from '@descript/web-vitals';

function sendToGoogleAnalytics({name, delta, value, id}) {
// Assumes the global `gtag()` function exists, see:
Expand Down Expand Up @@ -375,7 +375,7 @@ When using the [attribution build](#attribution-build), you can send additional
This example sends an additional `debug_target` param to Google Analytics, corresponding to the element most associated with each metric.

```js
import {onCLS, onFID, onLCP} from 'web-vitals/attribution';
import {onCLS, onFID, onLCP} from '@descript/web-vitals/attribution';

function sendToGoogleAnalytics({name, delta, value, id, attribution}) {
const eventParams = {
Expand Down Expand Up @@ -422,7 +422,7 @@ However, since not all Web Vitals metrics become available at the same time, and
Instead, you should keep a queue of all metrics that were reported and flush the queue whenever the page is backgrounded or unloaded:

```js
import {onCLS, onFID, onLCP} from 'web-vitals';
import {onCLS, onFID, onLCP} from '@descript/web-vitals';

const queue = new Set();
function addToQueue(metric) {
Expand Down Expand Up @@ -879,7 +879,7 @@ Note, this function waits until after the page is loaded to call `callback` in o
For example, the TTFB metric starts from the page's [time origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it includes time spent on DNS lookup, connection negotiation, network latency, and server processing time.

```js
import {onTTFB} from 'web-vitals';
import {onTTFB} from '@descript/web-vitals';

onTTFB((metric) => {
// Calculate the request time by subtracting from TTFB
Expand All @@ -899,7 +899,11 @@ The thresholds of each metric's "good", "needs improvement", and "poor" ratings
Example:

```ts
import {CLSThresholds, FIDThresholds, LCPThresholds} from 'web-vitals';
import {
CLSThresholds,
FIDThresholds,
LCPThresholds,
} from '@descript/web-vitals';

console.log(CLSThresholds); // [ 0.1, 0.25 ]
console.log(FIDThresholds); // [ 100, 300 ]
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-vitals",
"version": "3.5.2",
"name": "@descript/web-vitals",
"version": "3.5.2-descript.1",
"description": "Easily measure performance metrics in JavaScript",
"type": "module",
"typings": "dist/modules/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
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 {onINP, resetINP, INPThresholds} from './onINP.js';
export {onLCP, LCPThresholds} from './onLCP.js';
export {onTTFB, TTFBThresholds} from './onTTFB.js';

Expand Down
20 changes: 16 additions & 4 deletions src/onINP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,7 @@ export const onINP = (onReport: INPReportCallback, opts?: ReportOpts) => {
report(true);
});

// Only report after a bfcache restore if the `PerformanceObserver`
// successfully registered.
onBFCacheRestore(() => {
function resetMetric() {
longestInteractionList = [];
// Important, we want the count for the full page here,
// not just for the current navigation.
Expand All @@ -255,7 +253,21 @@ export const onINP = (onReport: INPReportCallback, opts?: ReportOpts) => {
INPThresholds,
opts!.reportAllChanges,
);
});
}

// Only report after a bfcache restore if the `PerformanceObserver`
// successfully registered.
onBFCacheRestore(resetMetric);

document.addEventListener('reset-web-vitals-inp', resetMetric);
}
});
};

/**
* Resets the INP metric to its initial state. Existing callbacks
* will be retained and called for the new metric.
*/
export const resetINP = () => {
document.dispatchEvent(new Event('reset-web-vitals-inp'));
};
30 changes: 30 additions & 0 deletions test/e2e/onINP-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,36 @@ describe('onINP()', async function () {
assert.strictEqual(inp.navigationType, 'prerender');
});

it('resets INP metric', async function () {
if (!browserSupportsINP) this.skip();

await navigateTo('/test/inp?click=100&reportAllChanges=1');

for (let i = 0; i < 10; i++) {
// should immediately report INP after resetting the metric
const resetMetric = await $('#reset-metric');
await resetMetric.click();

await stubVisibilityChange('hidden');

await beaconCountIs(1);

const [inp] = await getBeacons();
assert(inp.value >= 0);
assert(inp.id.match(/^v3-\d+-\d+$/));
assert.strictEqual(inp.name, 'INP');
assert.strictEqual(inp.value, inp.delta);
assert.strictEqual(inp.rating, 'good');
assert(containsEntry(inp.entries, 'click', '#reset-metric'));
assert(interactionIDsMatch(inp.entries));
assert(inp.entries[0].interactionId > 0);
assert.match(inp.navigationType, /navigate|reload/);

await clearBeacons();
await stubVisibilityChange('visible');
}
});

it('reports restore as nav type for wasDiscarded', async function () {
if (!browserSupportsINP) this.skip();

Expand Down
2 changes: 1 addition & 1 deletion test/unit/attribution-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
INPThresholds,
LCPThresholds,
TTFBThresholds,
} from 'web-vitals/attribution';
} from '@descript/web-vitals/attribution';

describe('index', () => {
it('exports Web Vitals metrics functions', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
INPThresholds,
LCPThresholds,
TTFBThresholds,
} from 'web-vitals';
} from '@descript/web-vitals';

describe('index', () => {
it('exports Web Vitals metrics functions', () => {
Expand Down
8 changes: 7 additions & 1 deletion test/views/inp.njk
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<p>
<button id="reset">Reset blocking time to zero</button>
</p>
<p>
<button id="reset-metric">Reset metric</button>
</p>

<p>
<textarea id="textarea" style="width:40em;height:5em"></textarea>
Expand All @@ -65,6 +68,7 @@

function block(event) {
const blockingTime = Number(document.getElementById(`${event.type}-blocking-time`).value);
console.log('Blocking for', blockingTime, 'ms');
const startTime = performance.now();
while (performance.now() < startTime + blockingTime) {
// Block...
Expand Down Expand Up @@ -116,7 +120,9 @@
return node?.id ? `#${node.id}` : node?.nodeName?.toLowerCase();
}

const {onINP} = await __testImport('{{ modulePath }}');
const {onINP, resetINP} = await __testImport('{{ modulePath }}');

document.getElementById('reset-metric').addEventListener('click', resetINP);

onINP((inp) => {
// Log for easier manual testing.
Expand Down
Loading