diff --git a/package.json b/package.json index ba7c9ab0..3fcd6a4f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "scripts": { "build": "pnpm -r --if-present build", "build:packages": "pnpm --filter './packages/*' -r build", - "test": "pnpm --filter './packages/*' -r --if-present test", + "test": "pnpm --filter './packages/*' -r --if-present test && pnpm --filter @layoutit/polycss-website test:analytics", "test:coverage": "pnpm --filter './packages/*' -r --if-present test:coverage", "sync:readmes": "node .github/scripts/sync-package-readmes.mjs", "sync:skill": "node .github/scripts/sync-skill-docs.mjs", diff --git a/website/astro.config.mjs b/website/astro.config.mjs index a917bf1c..94b91015 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -4,6 +4,7 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import sitemap from '@astrojs/sitemap'; import react from '@astrojs/react'; +import { googleAnalyticsBootstrap } from './src/googleAnalytics.mjs'; /** @param {string} path */ const repoPath = (path) => fileURLToPath(new URL(path, import.meta.url)); @@ -55,9 +56,8 @@ export default defineConfig({ favicon: '/favicon.ico', head: [ // Google Analytics (gtag.js) — covers all Starlight docs pages; custom - // pages render the same tag via src/components/Analytics.astro. - { tag: 'script', attrs: { async: true, src: 'https://www.googletagmanager.com/gtag/js?id=G-XV72TXWTM5' } }, - { tag: 'script', content: "window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-XV72TXWTM5');" }, + // pages render the same bootstrap via src/components/Analytics.astro. + { tag: 'script', content: googleAnalyticsBootstrap }, { tag: 'meta', attrs: { property: 'og:image', content: 'https://polycss.com/polycss-github.png' } }, { tag: 'meta', attrs: { property: 'og:image:width', content: '1280' } }, { tag: 'meta', attrs: { property: 'og:image:height', content: '640' } }, diff --git a/website/package.json b/website/package.json index 7d7a3b42..bb5dff9b 100644 --- a/website/package.json +++ b/website/package.json @@ -12,6 +12,7 @@ "prove:domformat": "node scripts/prove-gallery-domformat.mjs", "verify:domformat": "node scripts/verify-gallery-domformat.mjs", "verify:domformat:fresh": "node scripts/verify-gallery-domformat.mjs --fresh", + "test:analytics": "node --test test/googleAnalytics.test.mjs", "test:domformat-tools": "node --test scripts/gallery-domformat-canonical.test.mjs", "preview": "astro preview" }, diff --git a/website/src/components/Analytics.astro b/website/src/components/Analytics.astro index a9eee5aa..bbf063c7 100644 --- a/website/src/components/Analytics.astro +++ b/website/src/components/Analytics.astro @@ -1,13 +1,7 @@ --- // Google Analytics (gtag.js). Rendered in the of the custom Astro pages // (index / wordart / gallery / builder / 404). Starlight docs pages get the -// same tag via the `head` config in astro.config.mjs, so every page is covered. -const GA_ID = 'G-XV72TXWTM5'; +// same bootstrap via the `head` config in astro.config.mjs, so every page is covered. +import { googleAnalyticsBootstrap } from '../googleAnalytics.mjs'; --- - - + diff --git a/website/src/googleAnalytics.mjs b/website/src/googleAnalytics.mjs new file mode 100644 index 00000000..38266eac --- /dev/null +++ b/website/src/googleAnalytics.mjs @@ -0,0 +1,21 @@ +export const googleAnalyticsBootstrap = String.raw` +(function () { + var GA_ID = "G-XV72TXWTM5"; + var productionHosts = ["polycss.com", "www.polycss.com"]; + + if (productionHosts.indexOf(window.location.hostname) < 0) { + window["ga-disable-" + GA_ID] = true; + return; + } + + window.dataLayer = window.dataLayer || []; + window.gtag = window.gtag || function gtag() { window.dataLayer.push(arguments); }; + window.gtag("js", new Date()); + window.gtag("config", GA_ID); + + var tag = document.createElement("script"); + tag.async = true; + tag.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(GA_ID); + document.head.appendChild(tag); +})(); +`.trim(); diff --git a/website/test/googleAnalytics.test.mjs b/website/test/googleAnalytics.test.mjs new file mode 100644 index 00000000..9acd7230 --- /dev/null +++ b/website/test/googleAnalytics.test.mjs @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { runInNewContext } from 'node:vm'; + +import { googleAnalyticsBootstrap } from '../src/googleAnalytics.mjs'; + +test('analytics loads only on the production hosts', () => { + const local = runAnalyticsBootstrap('localhost'); + assert.equal(local.window['ga-disable-G-XV72TXWTM5'], true); + assert.equal(local.appendedTags.length, 0); + assert.equal(local.window.dataLayer, undefined); + + for (const hostname of ['polycss.com', 'www.polycss.com']) { + const production = runAnalyticsBootstrap(hostname); + assert.equal(production.window['ga-disable-G-XV72TXWTM5'], undefined); + assert.equal(production.appendedTags.length, 1); + assert.equal(production.appendedTags[0].async, true); + assert.equal(production.appendedTags[0].src, 'https://www.googletagmanager.com/gtag/js?id=G-XV72TXWTM5'); + assert.equal(production.window.dataLayer.length, 2); + } +}); + +function runAnalyticsBootstrap(hostname) { + const appendedTags = []; + const window = { location: { hostname } }; + const document = { + createElement: (tagName) => ({ tagName }), + head: { appendChild: (tag) => appendedTags.push(tag) }, + }; + runInNewContext(googleAnalyticsBootstrap, { Date, document, encodeURIComponent, window }); + return { appendedTags, window }; +}