From 30a89fa14156d6707b9a1aad1efd72cf8807e787 Mon Sep 17 00:00:00 2001 From: Justin Sun Date: Tue, 7 Feb 2023 13:04:21 +0800 Subject: [PATCH] feat(intergration/vercel): convert analytics to ts and support in edge --- packages/integrations/vercel/README.md | 2 +- packages/integrations/vercel/analytics.js | 63 ----------------- packages/integrations/vercel/package.json | 1 + packages/integrations/vercel/src/analytics.ts | 69 +++++++++++++++++++ .../integrations/vercel/src/edge/adapter.ts | 11 ++- .../vercel/src/serverless/adapter.ts | 5 +- 6 files changed, 81 insertions(+), 70 deletions(-) delete mode 100644 packages/integrations/vercel/analytics.js create mode 100644 packages/integrations/vercel/src/analytics.ts diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md index 593b0bb0abdf..99816190bed1 100644 --- a/packages/integrations/vercel/README.md +++ b/packages/integrations/vercel/README.md @@ -136,7 +136,7 @@ export default defineConfig({ ### analytics > **Type:** `boolean` -> **Available for:** Serverless +> **Available for:** Serverless, Edge Use this property to enable Vercel Analytics (including Web Vitals and Audiences). diff --git a/packages/integrations/vercel/analytics.js b/packages/integrations/vercel/analytics.js deleted file mode 100644 index cc75249f5df8..000000000000 --- a/packages/integrations/vercel/analytics.js +++ /dev/null @@ -1,63 +0,0 @@ -import { inject } from '@vercel/analytics'; -import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals'; - -const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals'; - -const getConnectionSpeed = () => { - return 'connection' in navigator && - navigator['connection'] && - 'effectiveType' in navigator['connection'] - ? navigator['connection']['effectiveType'] - : ''; -}; - -const sendToAnalytics = (metric, options) => { - const body = { - dsn: options.analyticsId, - id: metric.id, - page: options.path, - href: location.href, - event_name: metric.name, - value: metric.value.toString(), - speed: getConnectionSpeed(), - }; - - if (options.debug) { - console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2)); - } - - const blob = new Blob([new URLSearchParams(body).toString()], { - type: 'application/x-www-form-urlencoded', - }); - if (navigator.sendBeacon) { - navigator.sendBeacon(vitalsUrl, blob); - } else - fetch(vitalsUrl, { - body: blob, - method: 'POST', - credentials: 'omit', - keepalive: true, - }); -}; - -function webVitals() { - const options = { - path: window.location.pathname, - analyticsId: import.meta.env.PUBLIC_VERCEL_ANALYTICS_ID, - }; - try { - getFID((metric) => sendToAnalytics(metric, options)); - getTTFB((metric) => sendToAnalytics(metric, options)); - getLCP((metric) => sendToAnalytics(metric, options)); - getCLS((metric) => sendToAnalytics(metric, options)); - getFCP((metric) => sendToAnalytics(metric, options)); - } catch (err) { - console.error('[Analytics]', err); - } -} - -const mode = import.meta.env.MODE; -inject({ mode }); -if (mode === 'production') { - webVitals(); -} diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index 5c677f25b4f7..a147301fd359 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -22,6 +22,7 @@ "./serverless": "./dist/serverless/adapter.js", "./serverless/entrypoint": "./dist/serverless/entrypoint.js", "./static": "./dist/static/adapter.js", + "./analytics": "./dist/analytics.js", "./package.json": "./package.json" }, "typesVersions": { diff --git a/packages/integrations/vercel/src/analytics.ts b/packages/integrations/vercel/src/analytics.ts new file mode 100644 index 000000000000..4d254dc74361 --- /dev/null +++ b/packages/integrations/vercel/src/analytics.ts @@ -0,0 +1,69 @@ +import { inject } from '@vercel/analytics'; +import type { Metric } from 'web-vitals'; +import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals'; + +const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals'; + +type Options = { path: string; analyticsId: string; debug?: boolean }; + +const getConnectionSpeed = () => { + return 'connection' in navigator && + navigator['connection'] && + 'effectiveType' in (navigator['connection'] as unknown as { effectiveType: string }) + ? (navigator['connection'] as unknown as { effectiveType: string })['effectiveType'] + : ''; +}; + +const sendToAnalytics = (metric: Metric, options: Options) => { + const body = { + dsn: options.analyticsId, + id: metric.id, + page: options.path, + href: location.href, + event_name: metric.name, + value: metric.value.toString(), + speed: getConnectionSpeed(), + }; + + if (options.debug) { + // eslint-disable-next-line no-console + console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2)); + } + + const blob = new Blob([new URLSearchParams(body).toString()], { + // This content type is necessary for `sendBeacon` + type: 'application/x-www-form-urlencoded', + }); + if (navigator.sendBeacon) { + navigator.sendBeacon(vitalsUrl, blob); + } else + fetch(vitalsUrl, { + body: blob, + method: 'POST', + credentials: 'omit', + keepalive: true, + }); +}; + +function webVitals() { + const options = { + path: window.location.pathname, + analyticsId: (import.meta as any).env.PUBLIC_VERCEL_ANALYTICS_ID, + }; + try { + getFID((metric) => sendToAnalytics(metric, options)); + getTTFB((metric) => sendToAnalytics(metric, options)); + getLCP((metric) => sendToAnalytics(metric, options)); + getCLS((metric) => sendToAnalytics(metric, options)); + getFCP((metric) => sendToAnalytics(metric, options)); + } catch (err) { + console.error('[Analytics]', err); + } +} + +const mode = (import.meta as any).env.MODE as 'development' | 'production'; + +inject({ mode }); +if (mode === 'production') { + webVitals(); +} diff --git a/packages/integrations/vercel/src/edge/adapter.ts b/packages/integrations/vercel/src/edge/adapter.ts index 3f38a074e628..6517a97fd772 100644 --- a/packages/integrations/vercel/src/edge/adapter.ts +++ b/packages/integrations/vercel/src/edge/adapter.ts @@ -24,9 +24,13 @@ function getAdapter(): AstroAdapter { export interface VercelEdgeConfig { includeFiles?: string[]; + analytics?: boolean; } -export default function vercelEdge({ includeFiles = [] }: VercelEdgeConfig = {}): AstroIntegration { +export default function vercelEdge({ + includeFiles = [], + analytics, +}: VercelEdgeConfig = {}): AstroIntegration { let _config: AstroConfig; let buildTempFolder: URL; let functionFolder: URL; @@ -35,7 +39,10 @@ export default function vercelEdge({ includeFiles = [] }: VercelEdgeConfig = {}) return { name: PACKAGE_NAME, hooks: { - 'astro:config:setup': ({ config, updateConfig }) => { + 'astro:config:setup': ({ config, updateConfig, injectScript }) => { + if (analytics) { + injectScript('page', 'import "@astrojs/vercel/analytics"'); + } const outDir = getVercelOutput(config.root); updateConfig({ outDir, diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 8454ad39bbcc..c070bd72deff 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -38,10 +38,7 @@ export default function vercelServerless({ hooks: { 'astro:config:setup': ({ config, updateConfig, injectScript }) => { if (analytics) { - injectScript( - 'page', - readFileSync(new URL('../../analytics.js', import.meta.url), { encoding: 'utf-8' }) - ); + injectScript('page', 'import "@astrojs/vercel/analytics"'); } const outDir = getVercelOutput(config.root); updateConfig({