Skip to content

Commit

Permalink
Experimental: hook version of reportWebVitals
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Sep 3, 2021
1 parent fcdf732 commit 3ea62ac
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 15 deletions.
18 changes: 14 additions & 4 deletions packages/next/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@ import {
assign,
} from '../shared/lib/router/utils/querystring'
import { setConfig } from '../shared/lib/runtime-config'
import { getURL, loadGetInitialProps, NEXT_DATA, ST } from '../shared/lib/utils'
import {
getURL,
loadGetInitialProps,
NextWebVitalsMetric,
NEXT_DATA,
ST,
} from '../shared/lib/utils'
import { Portal } from './portal'
import initHeadManager from './head-manager'
import PageLoader, { StyleSheetTuple } from './page-loader'
import measureWebVitals from './performance-relayer'
import { RouteAnnouncer } from './route-announcer'
import { createRouter, makePublicRouterInstance } from './router'
import { webVitalsCallbacks } from '../vitals/vitals'

/// <reference types="react-dom/experimental" />

Expand Down Expand Up @@ -271,7 +278,8 @@ export async function initNext(opts: { webpackHMR?: any } = {}) {

const { component: app, exports: mod } = appEntrypoint
CachedApp = app as AppComponent
if (mod && mod.reportWebVitals) {
const exportedReportWebVitals = mod && mod.reportWebVitals
if (exportedReportWebVitals) {
onPerfEntry = ({
id,
name,
Expand All @@ -291,7 +299,7 @@ export async function initNext(opts: { webpackHMR?: any } = {}) {
perfStartEntry = entries[0].startTime
}

mod.reportWebVitals({
const webVitals: NextWebVitalsMetric = {
id: id || uniqueID,
name,
startTime: startTime || perfStartEntry,
Expand All @@ -300,7 +308,9 @@ export async function initNext(opts: { webpackHMR?: any } = {}) {
entryType === 'mark' || entryType === 'measure'
? 'custom'
: 'web-vital',
})
}
exportedReportWebVitals?.(webVitals)
webVitalsCallbacks.forEach((callback) => callback(webVitals))
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/next/taskfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ export async function compile(task, opts) {
'pages',
'lib',
'client',
'vitals',
'telemetry',
'shared',
'server_wasm',
Expand Down Expand Up @@ -999,6 +1000,14 @@ export async function client(task, opts) {
notify('Compiled client files')
}

export async function vitals(task, opts) {
await task
.source(opts.src || 'vitals/**/*.+(js|ts|tsx)')
.swc('vitals', { dev: opts.dev })
.target('dist/vitals')
notify('Compiled vitals files')
}

// export is a reserved keyword for functions
export async function nextbuildstatic(task, opts) {
await task
Expand Down Expand Up @@ -1055,6 +1064,7 @@ export default async function (task) {
await task.watch('build/**/*.+(js|ts|tsx)', 'nextbuild', opts)
await task.watch('export/**/*.+(js|ts|tsx)', 'nextbuildstatic', opts)
await task.watch('client/**/*.+(js|ts|tsx)', 'client', opts)
await task.watch('vitals/**/*.+(js|ts|tsx)', 'vitals', opts)
await task.watch('lib/**/*.+(js|ts|tsx)', 'lib', opts)
await task.watch('cli/**/*.+(js|ts|tsx)', 'cli', opts)
await task.watch('telemetry/**/*.+(js|ts|tsx)', 'telemetry', opts)
Expand Down
1 change: 1 addition & 0 deletions packages/next/vitals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/vitals/index'
1 change: 1 addition & 0 deletions packages/next/vitals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/vitals/index')
1 change: 1 addition & 0 deletions packages/next/vitals/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useWebVitalsReport } from './vitals'
18 changes: 18 additions & 0 deletions packages/next/vitals/vitals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect, useState } from 'react'
import { NextWebVitalsMetric } from '../pages/_app'

type ReportWebVitalsCallback = (webVitals: NextWebVitalsMetric) => any
export const webVitalsCallbacks = new Set<ReportWebVitalsCallback>()

export function useWebVitalsReport(callback: ReportWebVitalsCallback) {
// call on initial render, in a very early phase
useState(() => {
webVitalsCallbacks.add(callback)
})

useEffect(() => {
return () => {
webVitalsCallbacks.delete(callback)
}
})
}
14 changes: 14 additions & 0 deletions test/integration/relay-analytics/test/hook-impl-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* global localStorage */
import { useWebVitalsReport } from 'next/vitals'

export default function MyApp({ Component, pageProps }) {
useWebVitalsReport((data) => {
localStorage.setItem(
data.name || data.entryType,
data.value !== undefined ? data.value : data.startTime
)
})
return <Component {...pageProps} />
}

export function reportWebVitals() {}
60 changes: 49 additions & 11 deletions test/integration/relay-analytics/test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
/* eslint-env jest */

import { join } from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'
import { killApp, findPort, nextBuild, nextStart, check } from 'next-test-utils'
import {
File,
killApp,
findPort,
nextBuild,
nextStart,
check,
waitFor,
} from 'next-test-utils'

const appDir = join(__dirname, '../')
const customApp = new File(join(appDir, 'pages/_app.js'))
const hookImplCustomAppContent = fs.readFileSync(
join(__dirname, 'hook-impl-app.js'),
{ encoding: 'utf-8' }
)
let appPort
let server
let stdout
jest.setTimeout(1000 * 60 * 2)

async function buildApp() {
appPort = await findPort()
;({ stdout } = await nextBuild(appDir, [], {
env: { VERCEL_ANALYTICS_ID: 'test' },
stdout: true,
}))
server = await nextStart(appDir, appPort)
}
async function killServer() {
await killApp(server)
}

describe('Analytics relayer', () => {
let stdout
beforeAll(async () => {
appPort = await findPort()
;({ stdout } = await nextBuild(appDir, [], {
env: { VERCEL_ANALYTICS_ID: 'test' },
stdout: true,
}))
server = await nextStart(appDir, appPort)
describe('exported analytics', () => {
beforeAll(async () => await buildApp())
afterAll(async () => await killServer())
runTest()
})
afterAll(() => killApp(server))

describe('Hook based analytics', () => {
beforeAll(async () => {
customApp.write(hookImplCustomAppContent)
await buildApp()
})

afterAll(async () => {
customApp.restore()
await killServer()
})
runTest()
})
})

function runTest() {
it('Relays the data to user code', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('h1')

const h1Text = await browser.elementByCss('h1').text()
const data = parseFloat(
await browser.eval('localStorage.getItem("Next.js-hydration")')
Expand Down Expand Up @@ -87,4 +125,4 @@ describe('Analytics relayer', () => {
expect(stdout).toMatch('Next.js Analytics')
await browser.close()
})
})
}

0 comments on commit 3ea62ac

Please sign in to comment.