From f7d069458f5fabc3aa5889fc690736ba9b7061fa Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 30 Aug 2025 16:50:43 +0100 Subject: [PATCH] Update plausible --- package-lock.json | 14 ++ package.json | 1 + src/index.ts | 16 +- src/plausible/request.ts | 73 --------- src/plausible/tracker.ts | 334 --------------------------------------- tsconfig.json | 7 +- 6 files changed, 29 insertions(+), 416 deletions(-) delete mode 100644 src/plausible/request.ts delete mode 100644 src/plausible/tracker.ts diff --git a/package-lock.json b/package-lock.json index d95f2b2..f6efa97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "portfolio", "license": "GPL-3.0-or-later", "devDependencies": { + "@plausible-analytics/tracker": "^0.4.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "css-loader": "^6.8.1", @@ -809,6 +810,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@plausible-analytics/tracker": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@plausible-analytics/tracker/-/tracker-0.4.0.tgz", + "integrity": "sha512-KXwttotIZymo3yGzargrsxl9hjXJo5N+Kips3ZMamYqJxJqv1Zx+POC6WOFxYwDe1iJW7T91ItQYD8mZsznpXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.1.tgz", @@ -11369,6 +11377,12 @@ } } }, + "@plausible-analytics/tracker": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@plausible-analytics/tracker/-/tracker-0.4.0.tgz", + "integrity": "sha512-KXwttotIZymo3yGzargrsxl9hjXJo5N+Kips3ZMamYqJxJqv1Zx+POC6WOFxYwDe1iJW7T91ItQYD8mZsznpXQ==", + "dev": true + }, "@pnpm/network.ca-file": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.1.tgz", diff --git a/package.json b/package.json index 1c1aafd..6c85514 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ ], "homepage": "https://github.com/schmelczer/schmelczer.github.io#readme", "devDependencies": { + "@plausible-analytics/tracker": "^0.4.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "css-loader": "^6.8.1", diff --git a/src/index.ts b/src/index.ts index 8e0dcea..de231c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import '../static/no-change/favicons/favicon.ico'; import '../static/no-change/favicons/site.webmanifest'; import '../static/no-change/og-image.jpg'; import '../static/no-change/robots.txt'; +import { init as plausibleInit } from '@plausible-analytics/tracker' import { portfolio } from './data/portfolio'; import { addSupportForTabNavigation, @@ -15,16 +16,17 @@ import { } from './helper/accessibility'; import { scrollToFragment } from './helper/scroll-to-fragment'; import './index.scss'; -import Plausible from './plausible/tracker'; -const plausible = Plausible({ - hashMode: true, - trackLocalhost: true, - apiURI: 'https://stats.schmelczer.dev/status', +plausibleInit({ + domain: 'schmelczer.dev', + endpoint: 'https://stats.schmelczer.dev/status', + autoCapturePageviews: true, + captureOnLocalhost: true, + logging: true, + fileDownloads: true, + hashBasedRouting: true }); -plausible.enableAutoPageviews(); -plausible.enableAutoOutboundTracking(); addSupportForTabNavigation(); removeUnnecessaryOutlines(); diff --git a/src/plausible/request.ts b/src/plausible/request.ts deleted file mode 100644 index f17c429..0000000 --- a/src/plausible/request.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { PlausibleOptions } from './tracker'; - -type EventPayload = { - readonly n: string; - readonly u: Location['href']; - readonly d: Location['hostname']; - readonly r: Document['referrer'] | null; - readonly w: Window['innerWidth']; - readonly h: 1 | 0; - readonly p?: string; -}; - -export type EventOptions = { - /** - * Callback called when the event is successfully sent. - */ - readonly callback?: () => void; - /** - * Properties to be bound to the event. - */ - readonly props?: { readonly [propName: string]: string }; -}; - -/** - * @internal - * Sends an event to Plausible's API - * - * @param data - Event data to send - * @param options - Event options - */ -export function sendEvent( - eventName: string, - data: Required, - options?: EventOptions -): void { - const isLocalhost = - /^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*:)*?:?0*1$/.test( - window.location.hostname - ) || window.location.protocol === 'file:'; - - if (!data.trackLocalhost && isLocalhost) { - return console.warn('[Plausible] Ignoring event because website is running locally'); - } - - const shouldIgnoreCurrentBrowser = localStorage.getItem('plausible_ignore') === 'true'; - if (shouldIgnoreCurrentBrowser) { - return console.warn( - '[Plausible] Ignoring event because "plausible_ignore" is set to "true" in localStorage' - ); - } - - const payload: EventPayload = { - n: eventName, - u: data.url, - d: data.domain, - r: data.referrer, - w: data.deviceWidth, - h: data.hashMode ? 1 : 0, - p: options && options.props ? JSON.stringify(options.props) : undefined, - }; - - const req = new XMLHttpRequest(); - req.open('POST', data.apiURI, true); - req.setRequestHeader('Content-Type', 'text/plain'); - req.send(JSON.stringify(payload)); - - req.onreadystatechange = () => { - if (req.readyState !== 4) return; - if (options && options.callback) { - options.callback(); - } - }; -} diff --git a/src/plausible/tracker.ts b/src/plausible/tracker.ts deleted file mode 100644 index dc49490..0000000 --- a/src/plausible/tracker.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { EventOptions, sendEvent } from './request'; - -/** - * Options used when initializing the tracker. - */ -export type PlausibleInitOptions = { - /** - * If true, pageviews will be tracked when the URL hash changes. - * Enable this if you are using a frontend that uses hash-based routing. - */ - readonly hashMode?: boolean; - /** - * Set to true if you want events to be tracked when running the site locally. - */ - readonly trackLocalhost?: boolean; - /** - * The domain to bind the event to. - * Defaults to `location.hostname` - */ - readonly domain?: Location['hostname']; - /** - * The API host where the events will be sent. - * Defaults to `'https://plausible.io/api/event'` - */ - readonly apiURI?: string; -}; - -/** - * Data passed to Plausible as events. - */ -export type PlausibleEventData = { - /** - * The URL to bind the event to. - * Defaults to `location.href`. - */ - readonly url?: Location['href']; - /** - * The referrer to bind the event to. - * Defaults to `document.referrer` - */ - readonly referrer?: Document['referrer'] | null; - /** - * The current device's width. - * Defaults to `window.innerWidth` - */ - readonly deviceWidth?: Window['innerWidth']; -}; - -/** - * Options used when tracking Plausible events. - */ -export type PlausibleOptions = PlausibleInitOptions & PlausibleEventData; - -/** - * Tracks a custom event. - * - * Use it to track your defined goals by providing the goal's name as `eventName`. - * - * ### Example - * ```js - * import Plausible from 'plausible-tracker' - * - * const { trackEvent } = Plausible() - * - * // Tracks the 'signup' goal - * trackEvent('signup') - * - * // Tracks the 'Download' goal passing a 'method' property. - * trackEvent('Download', { props: { method: 'HTTP' } }) - * ``` - * - * @param eventName - Name of the event to track - * @param options - Event options. - * @param eventData - Optional event data to send. Defaults to the current page's data merged with the default options provided earlier. - */ -type TrackEvent = ( - eventName: string, - options?: EventOptions, - eventData?: PlausibleOptions -) => void; - -/** - * Manually tracks a page view. - * - * ### Example - * ```js - * import Plausible from 'plausible-tracker' - * - * const { trackPageview } = Plausible() - * - * // Track a page view - * trackPageview() - * ``` - * - * @param eventData - Optional event data to send. Defaults to the current page's data merged with the default options provided earlier. - * @param options - Event options. - */ -type TrackPageview = (eventData?: PlausibleOptions, options?: EventOptions) => void; - -/** - * Cleans up all event listeners attached. - */ -type Cleanup = () => void; - -/** - * Tracks the current page and all further pages automatically. - * - * Call this if you don't want to manually manage pageview tracking. - * - * ### Example - * ```js - * import Plausible from 'plausible-tracker' - * - * const { enableAutoPageviews } = Plausible() - * - * // This tracks the current page view and all future ones as well - * enableAutoPageviews() - * ``` - * - * The returned value is a callback that removes the added event listeners and restores `history.pushState` - * ```js - * import Plausible from 'plausible-tracker' - * - * const { enableAutoPageviews } = Plausible() - * - * const cleanup = enableAutoPageviews() - * - * // Remove event listeners and restore `history.pushState` - * cleanup() - * ``` - */ -type EnableAutoPageviews = () => Cleanup; - -/** - * Tracks all outbound link clicks automatically - * - * Call this if you don't want to manually manage these links. - * - * It works using a **[MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)** to automagically detect link nodes throughout your application and bind `click` events to them. - * - * Optionally takes the same parameters as [`MutationObserver.observe`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe). - * - * ### Example - * ```js - * import Plausible from 'plausible-tracker' - * - * const { enableAutoOutboundTracking } = Plausible() - * - * // This tracks all the existing and future outbound links on your page. - * enableAutoOutboundTracking() - * ``` - * - * The returned value is a callback that removes the added event listeners and disconnects the observer - * ```js - * import Plausible from 'plausible-tracker' - * - * const { enableAutoOutboundTracking } = Plausible() - * - * const cleanup = enableAutoOutboundTracking() - * - * // Remove event listeners and disconnect the observer - * cleanup() - * ``` - */ -type EnableAutoOutboundTracking = ( - targetNode?: Node & ParentNode, - observerInit?: MutationObserverInit -) => Cleanup; - -/** - * Initializes the tracker with your default values. - * - * ### Example (es module) - * ```js - * import Plausible from 'plausible-tracker' - * - * const { enableAutoPageviews, trackEvent } = Plausible({ - * domain: 'my-app-domain.com', - * hashMode: true - * }) - * - * enableAutoPageviews() - * - * function onUserRegister() { - * trackEvent('register') - * } - * ``` - * - * ### Example (commonjs) - * ```js - * var Plausible = require('plausible-tracker'); - * - * var { enableAutoPageviews, trackEvent } = Plausible({ - * domain: 'my-app-domain.com', - * hashMode: true - * }) - * - * enableAutoPageviews() - * - * function onUserRegister() { - * trackEvent('register') - * } - * ``` - * - * @param defaults - Default event parameters that will be applied to all requests. - */ -export default function Plausible(defaults?: PlausibleInitOptions): { - readonly trackEvent: TrackEvent; - readonly trackPageview: TrackPageview; - readonly enableAutoPageviews: EnableAutoPageviews; - readonly enableAutoOutboundTracking: EnableAutoOutboundTracking; -} { - const getConfig = (): Required => ({ - hashMode: false, - trackLocalhost: false, - url: window.location.href, - domain: window.location.hostname, - referrer: document.referrer || null, - deviceWidth: window.innerWidth, - apiURI: 'https://plausible.io/api/event/', - ...defaults, - }); - - const trackEvent: TrackEvent = (eventName, options, eventData) => { - sendEvent(eventName, { ...getConfig(), ...eventData }, options); - }; - - const trackPageview: TrackPageview = (eventData, options) => { - trackEvent('pageview', options, eventData); - }; - - const enableAutoPageviews: EnableAutoPageviews = () => { - const page = () => trackPageview(); - // Attach pushState and popState listeners - const originalPushState = window.history.pushState; - if (originalPushState) { - window.history.pushState = function (data, title, url) { - originalPushState.apply(this, [data, title, url]); - page(); - }; - window.addEventListener('popstate', page); - } - - // Attach hashchange listener - if (defaults && defaults.hashMode) { - window.addEventListener('hashchange', page); - } - - // Trigger first page view - trackPageview(); - - return function cleanup() { - if (originalPushState) { - window.history.pushState = originalPushState; - window.removeEventListener('popstate', page); - } - if (defaults && defaults.hashMode) { - window.removeEventListener('hashchange', page); - } - }; - }; - - const enableAutoOutboundTracking: EnableAutoOutboundTracking = ( - targetNode: Node & ParentNode = document, - observerInit: MutationObserverInit = { - subtree: true, - childList: true, - attributes: true, - attributeFilter: ['href'], - } - ) => { - function trackClick(this: HTMLAnchorElement, _event: MouseEvent) { - trackEvent('Outbound Link: Click', { props: { url: this.href } }); - } - - const tracked: Set = new Set(); - - function addNode(node: Node | ParentNode) { - if (node instanceof HTMLAnchorElement) { - if (node.host !== window.location.host) { - node.addEventListener('click', trackClick); - tracked.add(node); - } - } /* istanbul ignore next */ else if ('querySelectorAll' in node) { - node.querySelectorAll('a').forEach(addNode); - } - } - - function removeNode(node: Node | ParentNode) { - if (node instanceof HTMLAnchorElement) { - node.removeEventListener('click', trackClick); - tracked.delete(node); - } /* istanbul ignore next */ else if ('querySelectorAll' in node) { - node.querySelectorAll('a').forEach(removeNode); - } - } - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes') { - // Handle changed href - removeNode(mutation.target); - addNode(mutation.target); - } /* istanbul ignore next */ else if (mutation.type === 'childList') { - // Handle added nodes - mutation.addedNodes.forEach(addNode); - // Handle removed nodes - mutation.removedNodes.forEach(removeNode); - } - }); - }); - - // Track existing nodes - targetNode.querySelectorAll('a').forEach(addNode); - - // Observe mutations - observer.observe(targetNode, observerInit); - - return function cleanup() { - tracked.forEach((a) => { - a.removeEventListener('click', trackClick); - }); - tracked.clear(); - observer.disconnect(); - }; - }; - - return { - trackEvent, - trackPageview, - enableAutoPageviews, - enableAutoOutboundTracking, - }; -} diff --git a/tsconfig.json b/tsconfig.json index e01ac23..d34a2e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,11 @@ "module": "es6", "target": "es5", "sourceMap": true, + "moduleResolution": "node", "strict": true, "allowJs": true, - "lib": ["dom"] + "lib": [ + "dom" + ] } -} +} \ No newline at end of file