add method to measure Interaction to Next Paint (INP) (#36490)

This commit lets users measure their Interaction to Next Paint [INP](https://web.dev/inp/) web vital.
Note that the `web-vitals` package is beta to denote that INP is an experimental metric, the code is stable and v3 is backwards compatible.

    `web-vitals` CHANGELOG for v3:
    
    - [BREAKING] Report TTFB after a bfcache restore
    - [BREAKING] Only include last LCP entry in metric entries
    - Add support for the new INP metric
    - Rename getXXX() functions to onXXX()
    - Add a navigationType property to the Metric object
    
    See https://github.com/GoogleChrome/web-vitals/blob/next/CHANGELOG.md

Upgraded `playwright-chromium` from `1.14.1` to `1.17.2` because the Events Timing API used to measure INP is only available in Chromium >= v98.



## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
This commit is contained in:
Keen Yee Liau 2022-06-07 11:28:58 -07:00 committed by GitHub
parent 42f838e156
commit dc36199b22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 24 deletions

View file

@ -1,17 +1,18 @@
/* global location */
import {
getCLS,
getFCP,
getFID,
getLCP,
getTTFB,
onCLS,
onFCP,
onFID,
onINP,
onLCP,
onTTFB,
Metric,
ReportHandler,
ReportCallback,
} from 'next/dist/compiled/web-vitals'
const initialHref = location.href
let isRegistered = false
let userReportHandler: ReportHandler | undefined
let userReportHandler: ReportCallback | undefined
function onReport(metric: Metric): void {
if (userReportHandler) {
@ -71,7 +72,7 @@ function onReport(metric: Metric): void {
}
}
export default (onPerfEntry?: ReportHandler): void => {
export default (onPerfEntry?: ReportCallback): void => {
// Update function if it changes:
userReportHandler = onPerfEntry
@ -81,9 +82,10 @@ export default (onPerfEntry?: ReportHandler): void => {
}
isRegistered = true
getCLS(onReport)
getFID(onReport)
getFCP(onReport)
getLCP(onReport)
getTTFB(onReport)
onCLS(onReport)
onFID(onReport)
onFCP(onReport)
onLCP(onReport)
onTTFB(onReport)
onINP(onReport)
}

File diff suppressed because one or more lines are too long

View file

@ -270,7 +270,7 @@
"uuid": "8.3.2",
"vm-browserify": "1.1.2",
"watchpack": "2.4.0",
"web-vitals": "2.1.0",
"web-vitals": "3.0.0-beta.2",
"webpack-sources1": "npm:webpack-sources@1.4.3",
"webpack-sources3": "npm:webpack-sources@3.2.3",
"webpack4": "npm:webpack@4.44.1",

View file

@ -47,7 +47,7 @@ export type NextWebVitalsMetric = {
} & (
| {
label: 'web-vital'
name: 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB'
name: 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB' | 'INP'
}
| {
label: 'custom'

View file

@ -565,7 +565,7 @@ importers:
uuid: 8.3.2
vm-browserify: 1.1.2
watchpack: 2.4.0
web-vitals: 2.1.0
web-vitals: 3.0.0-beta.2
webpack-sources1: npm:webpack-sources@1.4.3
webpack-sources3: npm:webpack-sources@3.2.3
webpack4: npm:webpack@4.44.1
@ -753,7 +753,7 @@ importers:
uuid: 8.3.2
vm-browserify: 1.1.2
watchpack: 2.4.0
web-vitals: 2.1.0
web-vitals: 3.0.0-beta.2
webpack-sources1: /webpack-sources/1.4.3
webpack-sources3: /webpack-sources/3.2.3
webpack4: /webpack/4.44.1
@ -1058,7 +1058,7 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/compat-data': 7.17.0
'@babel/compat-data': 7.17.10
'@babel/core': 7.17.5
'@babel/helper-validator-option': 7.16.7
browserslist: 4.20.2
@ -1071,7 +1071,7 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/compat-data': 7.17.0
'@babel/compat-data': 7.17.10
'@babel/core': 7.18.0
'@babel/helper-validator-option': 7.16.7
browserslist: 4.20.2
@ -8823,7 +8823,7 @@ packages:
mississippi: 3.0.0
mkdirp: 0.5.5
move-concurrently: 1.0.1
promise-inflight: 1.0.1
promise-inflight: 1.0.1_bluebird@3.7.2
rimraf: 2.7.1
ssri: 6.0.1
unique-filename: 1.1.1
@ -15129,7 +15129,7 @@ packages:
dev: true
/json-schema/0.2.3:
resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=}
resolution: {integrity: sha512-a3xHnILGMtk+hDOqNwHzF6e2fNbiMrXZvxKQiEv2MlgQP+pjIOzqAmKYD2mDpXYE/44M7g+n9p2bKkYWDUcXCQ==}
dev: true
/json-stable-stringify-without-jsonify/1.0.1:
@ -19125,6 +19125,17 @@ packages:
optional: true
dev: true
/promise-inflight/1.0.1_bluebird@3.7.2:
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
bluebird: '*'
peerDependenciesMeta:
bluebird:
optional: true
dependencies:
bluebird: 3.7.2
dev: true
/promise-polyfill/6.1.0:
resolution: {integrity: sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=}
dev: true
@ -23107,8 +23118,8 @@ packages:
resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==}
dev: true
/web-vitals/2.1.0:
resolution: {integrity: sha512-npEyJP8jHf3J71t1tRTEtz9FeKp8H2udWJUUq5ykfPhhstr//TUxiYhIEzLNwk4zv2ybAilMn7v7N6Mxmuitmg==}
/web-vitals/3.0.0-beta.2:
resolution: {integrity: sha512-W9OALsWK4RkA5GWvLhsfszy+Q29WJBB27Dnucc3eYP6/0kz1XsfMgm+4au9X/KjXMIo92ZRU1fWBaSdNsaVjJg==}
dev: true
/webidl-conversions/3.0.1:

View file

@ -16,12 +16,21 @@ if (typeof navigator !== 'undefined') {
}
}
function toggleText(e) {
const startTime = performance.now()
while (performance.now() < startTime + 100) {
// busy waiting
}
e.target.textContent = e.target.textContent === 'Click' ? 'Press' : 'Click'
}
export default () => {
// Below comment will be used for replacing exported report method with hook based one.
return (
<div>
<h1>Foo!</h1>
<h2>bar!</h2>
<button onClick={toggleText}>Click</button>
</div>
)
}

View file

@ -108,4 +108,18 @@ function runTest() {
expect(stdout).toMatch('Next.js Analytics')
await browser.close()
})
it('reports INP metric', async () => {
const browser = await webdriver(appPort, '/')
await browser.elementByCss('button').click()
await browser.waitForCondition(
'document.querySelector("button").textContent === "Press"'
)
// INP metric is only reported on pagehide or visibilitychange event, so refresh the page
await browser.refresh()
const INP = parseInt(await browser.eval('localStorage.getItem("INP")'), 10)
// We introduced a delay of 100ms, so INP duration should be >= 100
expect(INP).toBeGreaterThanOrEqual(100)
await browser.close()
})
}