From 3c7a4fabea5ebb0e8f79742731415136ae3da9a6 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Sat, 18 May 2024 16:33:14 +0200 Subject: [PATCH] Upgrade `web-vitals` to v4 (#11094) --- .changeset/many-icons-kiss.md | 5 ++ packages/integrations/web-vitals/package.json | 2 +- .../web-vitals/src/client-script.ts | 4 +- .../integrations/web-vitals/src/schemas.ts | 6 +- .../web-vitals/test/basics.test.js | 59 +++++++++++++++---- .../fixtures/basics/src/pages/rows.json.ts | 8 +++ pnpm-lock.yaml | 8 ++- 7 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 .changeset/many-icons-kiss.md create mode 100644 packages/integrations/web-vitals/test/fixtures/basics/src/pages/rows.json.ts diff --git a/.changeset/many-icons-kiss.md b/.changeset/many-icons-kiss.md new file mode 100644 index 000000000000..f89dca5edd71 --- /dev/null +++ b/.changeset/many-icons-kiss.md @@ -0,0 +1,5 @@ +--- +"@astrojs/web-vitals": minor +--- + +Upgrades the `web-vitals` dependency to v4 and stops collecting data for the deprecated FID (First Input Delay) metric. diff --git a/packages/integrations/web-vitals/package.json b/packages/integrations/web-vitals/package.json index 931a742d288a..d4ee68ff0323 100644 --- a/packages/integrations/web-vitals/package.json +++ b/packages/integrations/web-vitals/package.json @@ -32,7 +32,7 @@ "test": "astro-scripts test --timeout 50000 \"test/**/*.test.js\"" }, "dependencies": { - "web-vitals": "^3.5.2" + "web-vitals": "^4.0.0" }, "peerDependencies": { "@astrojs/db": "^0.11.0" diff --git a/packages/integrations/web-vitals/src/client-script.ts b/packages/integrations/web-vitals/src/client-script.ts index b69fa6772eb9..86591745e946 100644 --- a/packages/integrations/web-vitals/src/client-script.ts +++ b/packages/integrations/web-vitals/src/client-script.ts @@ -1,4 +1,4 @@ -import { type Metric, onCLS, onFCP, onFID, onINP, onLCP, onTTFB } from 'web-vitals'; +import { type Metric, onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals'; import { WEB_VITALS_ENDPOINT_PATH } from './constants.js'; import type { ClientMetric } from './schemas.js'; @@ -26,7 +26,7 @@ function flushQueue() { queue.clear(); } -for (const listener of [onCLS, onLCP, onINP, onFID, onFCP, onTTFB]) { +for (const listener of [onCLS, onLCP, onINP, onFCP, onTTFB]) { listener(addToQueue); } diff --git a/packages/integrations/web-vitals/src/schemas.ts b/packages/integrations/web-vitals/src/schemas.ts index 7a2050bd53ae..6dcdbe0cc3af 100644 --- a/packages/integrations/web-vitals/src/schemas.ts +++ b/packages/integrations/web-vitals/src/schemas.ts @@ -7,10 +7,10 @@ const MetricTypeSchema = z.enum(['CLS', 'INP', 'LCP', 'FCP', 'FID', 'TTFB']); const MetricIdSchema = z .string() // Match https://github.com/GoogleChrome/web-vitals/blob/main/src/lib/generateUniqueID.ts - .regex(/^v3-\d{13}-\d{13}$/) + .regex(/^v4-\d{13}-\d{13}$/) // Avoid collecting higher resolution timestamp in ID. - // Transforms `'v3-1711484350895-3748043125387'` to `'v3-17114843-3748043125387'` - .transform((id) => id.replace(/^(v3-\d{8})\d{5}(-\d{13})$/, '$1$2')); + // Transforms `'v4-1711484350895-3748043125387'` to `'v4-17114843-3748043125387'` + .transform((id) => id.replace(/^(v4-\d{8})\d{5}(-\d{13})$/, '$1$2')); /** Shape of the data submitted from clients to the collection API. */ const ClientMetricSchema = z.object({ diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index 937619b483d4..43814b56dcd9 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -5,6 +5,12 @@ import { after, before, beforeEach, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; import { loadFixture } from './test-utils.js'; +function startOfHourISOString() { + const date = new Date(); + date.setMinutes(0, 0, 0); + return date.toISOString(); +} + /** * @template {Record void>} T * @template {keyof T} K @@ -94,25 +100,54 @@ describe('Web Vitals integration basics', () => { assert.equal(call.name, 'ZodError'); }); - it('inserts data via the injected endpoint', async () => { - const res = await fixture.fetch('/_web-vitals', { - method: 'POST', - body: JSON.stringify([ + describe('inserting data via the injected endpoint', () => { + /** @type {Response} */ + let res; + before(async () => { + res = await fixture.fetch('/_web-vitals', { + method: 'POST', + body: JSON.stringify([ + { + pathname: '/', + route: '/', + name: 'CLS', + id: 'v4-1711484350895-3748043125387', + value: 0, + rating: 'good', + }, + ]), + }); + }); + + it('inserting data does not error', () => { + assert.equal(res.status, 200); + assert.equal( + consoleErrorMock.calls.length, + 0, + 'Endpoint logged errors:\n' + consoleErrorMock.calls[0]?.join(' ') + ); + }) + + it('inserted data can be retrieved from the database', async () => { + const dbRows = await fixture.fetch('/rows.json', {}).then((r) => r.json()); + assert.deepEqual(dbRows, [ { pathname: '/', route: '/', name: 'CLS', - id: 'v3-1711484350895-3748043125387', + id: 'v4-17114843-3748043125387', value: 0, rating: 'good', + timestamp: startOfHourISOString(), }, - ]), + ]); }); - assert.equal(res.status, 200); - assert.equal( - consoleErrorMock.calls.length, - 0, - 'Endpoint logged errors:\n' + consoleErrorMock.calls[0]?.join(' ') - ); + }); + + it('inserted data uses a truncated timestamp in the ID', async () => { + // The IDs generated by the `web-vitals` package include a high resolution timestamp as the second portion, + // e.g. 'v4-1711484350895-3748043125387'. We reduce this data to an hourly resolution to lessen privacy concerns. + const dbRows = await fixture.fetch('/rows.json', {}).then((r) => r.json()); + assert.deepEqual(dbRows[0].id, 'v4-17114843-3748043125387'); }); }); diff --git a/packages/integrations/web-vitals/test/fixtures/basics/src/pages/rows.json.ts b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/rows.json.ts new file mode 100644 index 000000000000..1a829b91546c --- /dev/null +++ b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/rows.json.ts @@ -0,0 +1,8 @@ +import { db, AstrojsWebVitals_Metric } from "astro:db"; + +export const prerender = false; + +export async function GET() { + const rows = await db.select().from(AstrojsWebVitals_Metric).all(); + return new Response(JSON.stringify(rows)); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec4f799fe46d..d2ec86c1f0d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5420,8 +5420,8 @@ importers: packages/integrations/web-vitals: dependencies: web-vitals: - specifier: ^3.5.2 - version: 3.5.2 + specifier: ^4.0.0 + version: 4.0.0 devDependencies: '@astrojs/db': specifier: workspace:* @@ -17276,6 +17276,10 @@ packages: resolution: {integrity: sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==} dev: false + /web-vitals@4.0.0: + resolution: {integrity: sha512-8wQd4jkwFRwY5q3yAmHZAJ5MjnKR1vRACK+g2OEC8nUqi0WOxBrXfOxGNlJ+QtxzzSn/TkQO58wkW0coE68n0Q==} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}