UNPKG

@technoapple/ga4

Version:

TypeScript Node.js library to support GA4 analytics.

386 lines (278 loc) 10.6 kB
# @technoapple/ga4 A lightweight TypeScript GA4 helper for browser apps. It provides: - A simple GA4 wrapper (`init`, `send`, direct `gtag` access) - A `dataLayer` helper (`dataLayerHelper.get`) - Opt-in automatic tracking plugins (event delegation, outbound tracking, SPA page views, impression tracking, URL cleanup, media query tracking) ## Why this library GA4 and `gtag.js` are flexible, but many teams still need reusable browser-side utilities: - Keep initialization and event sending consistent - Add automatic tracking without wiring listeners repeatedly - Keep plugin logic tree-shakeable and removable ## Installation ```bash npm install @technoapple/ga4 ``` ## Quick Start ```ts import { ga4 } from '@technoapple/ga4'; ga4.init({ targetId: 'G-XXXXXXX' }); ga4.send('sign_up', { method: 'email', plan: 'pro', }); ``` ## Framework Examples Use these integration guides for copy-paste setup patterns and lifecycle notes: - [React example](docs/examples/react.md) - [Vue example](docs/examples/vue.md) - [Vanilla JavaScript example](docs/examples/vanilla.md) ## API ### `ga4.init(option)` Initializes `window.dataLayer` and `window.gtag`, then sends: - `gtag('js', new Date())` - `gtag('config', option.targetId)` ```ts ga4.init({ targetId: 'G-XXXXXXX' }); ``` ### `ga4.send(eventName, eventParameters)` Sends a GA4 event through `gtag('event', ...)`. ```ts ga4.send('purchase', { transaction_id: 'order_123', value: 99, currency: 'USD', }); ``` ### `ga4.gtag` Direct access to the typed `gtag` function. ```ts ga4.gtag('event', 'login', { method: 'google' }); ``` ### `ga4.use(PluginClass, options?)` Registers a plugin and returns its instance. ```ts import { ga4, OutboundLinkTracker } from '@technoapple/ga4'; const tracker = ga4.use(OutboundLinkTracker, { eventName: 'outbound_link_click', }); // Later tracker.remove(); ``` ### `ga4.removeAll()` Unregisters all plugins and calls each plugin's `remove()`. ### `dataLayerHelper.get(key, getLast?)` Reads values from `window.dataLayer`. - `getLast` omitted or `false`: returns first matching value - `getLast` set to `true`: returns last matching value ```ts import { dataLayerHelper } from '@technoapple/ga4'; const firstValue = dataLayerHelper.get('campaign'); const latestValue = dataLayerHelper.get('campaign', true); ``` ## Plugin Catalog All plugins implement: ```ts interface GA4Plugin { remove(): void; } ``` ### `EventTracker` Declarative DOM tracking via attributes (default prefix: `data-ga4-`). Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `events` | `string[]` | `['click']` | DOM events to listen to via delegation | | `attributePrefix` | `string` | `'data-ga4-'` | Reads attributes like `data-ga4-on` and `data-ga4-event-name` | | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip sending | Defaults: - `events: ['click']` - `attributePrefix: 'data-ga4-'` Example: ```html <button data-ga4-on="click" data-ga4-event-name="video_play" data-ga4-video-title="Summer Launch"> Play </button> ``` ```ts import { ga4, EventTracker } from '@technoapple/ga4'; ga4.use(EventTracker); ``` ### `OutboundLinkTracker` Tracks clicks on links whose hostname differs from `location.hostname`. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `events` | `string[]` | `['click']` | Event types for delegated tracking | | `linkSelector` | `string` | `'a, area'` | CSS selector for trackable links | | `shouldTrackOutboundLink` | `(link, parseUrl) => boolean` | Built-in external-hostname matcher | Override outbound detection logic | | `eventName` | `string` | `'outbound_link_click'` | Custom event name | | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to cancel event | Defaults: - `events: ['click']` - `linkSelector: 'a, area'` - `eventName: 'outbound_link_click'` Default params: - `link_url` - `link_domain` - `outbound: true` ### `OutboundFormTracker` Tracks form submissions whose `form.action` points to an external hostname. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `formSelector` | `string` | `'form'` | CSS selector for forms to observe | | `shouldTrackOutboundForm` | `(form, parseUrl) => boolean` | Built-in external-hostname matcher | Override outbound detection logic | | `eventName` | `string` | `'outbound_form_submit'` | Custom event name | | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to cancel event | Defaults: - `formSelector: 'form'` - `eventName: 'outbound_form_submit'` Default params: - `form_action` - `form_domain` - `outbound: true` ### `PageVisibilityTracker` Tracks visible/hidden durations using `document.visibilityState`. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `sendInitialPageview` | `boolean` | `false` | Sends initial `page_view` when page is visible | | `sessionTimeout` | `number` | `30` | Minutes before visible return triggers new `page_view` | | `eventName` | `string` | `'page_visibility'` | Custom visibility event name | | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event | Defaults: - `sendInitialPageview: false` - `sessionTimeout: 30` (minutes) - `eventName: 'page_visibility'` Default params: - `visibility_state` - `visibility_duration` - `page_path` ### `UrlChangeTracker` Tracks SPA navigation by patching `history.pushState`, optional `replaceState`, and listening to `popstate`. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `shouldTrackUrlChange` | `(newPath, oldPath) => boolean` | `newPath !== oldPath` | Decide when to emit `page_view` | | `trackReplaceState` | `boolean` | `false` | Also patch `history.replaceState` | | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event | Defaults: - `trackReplaceState: false` Sends `page_view` with: - `page_path` - `page_title` - `page_location` ### `ImpressionTracker` Tracks element impressions with `IntersectionObserver` and dynamic DOM changes via `MutationObserver`. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `elements` | `Array<string \| ImpressionElementConfig>` | `[]` | Element IDs or config objects to observe | | `rootMargin` | `string` | `'0px'` | Passed to `IntersectionObserver` | | `attributePrefix` | `string` | `'data-ga4-'` | Reads matching element attributes into params | | `eventName` | `string` | `'element_impression'` | Custom event name | | `hitFilter` | `(params, element) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event | Defaults: - `rootMargin: '0px'` - `attributePrefix: 'data-ga4-'` - `eventName: 'element_impression'` Can observe with element IDs or configs: ```ts import { ga4, ImpressionTracker } from '@technoapple/ga4'; ga4.use(ImpressionTracker, { elements: [ 'hero-banner', { id: 'cta-block', threshold: 0.5, trackFirstImpressionOnly: true }, ], }); ``` ### `CleanUrlTracker` Intercepts `gtag` calls for `config` and `page_view` payloads and normalizes: - `page_location` - `page_path` Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `stripQuery` | `boolean` | `false` | Remove all query params unless allowlist is set | | `queryParamsAllowlist` | `string[]` | `undefined` | Keep only selected params when stripping query | | `queryParamsDenylist` | `string[]` | `undefined` | Remove selected params when not stripping query | | `trailingSlash` | `'add' \| 'remove'` | `undefined` | Normalize `page_path` slash behavior | | `urlFilter` | `(url) => string` | `undefined` | Final custom URL transform | Options include: - `stripQuery` - `queryParamsAllowlist` - `queryParamsDenylist` - `trailingSlash: 'add' | 'remove'` - `urlFilter` ### `MediaQueryTracker` Tracks responsive breakpoint changes using `matchMedia`. Options matrix: | Option | Type | Default | Notes | |---|---|---|---| | `definitions` | `MediaQueryDefinition[]` | `[]` | Named breakpoint sets to track | | `changeTemplate` | `(oldValue, newValue) => string` | `${oldValue} => ${newValue}` | Label formatter for change payload | | `changeTimeout` | `number` | `1000` | Debounce delay in ms | | `eventName` | `string` | `'media_query_change'` | Custom event name | | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event | Defaults: - `changeTimeout: 1000` - `eventName: 'media_query_change'` Default params: - `media_query_name` - `media_query_value` - `media_query_change` ## Plugin Lifecycle Use either approach: ```ts const plugin = ga4.use(EventTracker); plugin.remove(); ``` ```ts ga4.removeAll(); ``` ## Full Example ```ts import { ga4, EventTracker, OutboundLinkTracker, UrlChangeTracker, CleanUrlTracker, } from '@technoapple/ga4'; ga4.init({ targetId: 'G-XXXXXXX' }); ga4.use(CleanUrlTracker, { stripQuery: true, queryParamsAllowlist: ['utm_source', 'utm_medium', 'utm_campaign'], trailingSlash: 'remove', }); ga4.use(EventTracker, { events: ['click', 'submit'] }); ga4.use(OutboundLinkTracker); ga4.use(UrlChangeTracker, { trackReplaceState: true }); ga4.send('app_initialized', { env: 'production' }); ``` ## TypeScript Support The package ships type definitions and exports plugin option types, including: - `EventTrackerOptions` - `OutboundLinkTrackerOptions` - `OutboundFormTrackerOptions` - `PageVisibilityTrackerOptions` - `UrlChangeTrackerOptions` - `ImpressionTrackerOptions` - `CleanUrlTrackerOptions` - `MediaQueryTrackerOptions` ## Development ```bash npm run build npm test npm run test:coverage ``` ## Notes - Browser-focused library: APIs rely on `window`, `document`, and browser events. - Call `ga4.init(...)` before sending events or registering plugins. - If you register many plugins, clean them up with `remove()` or `ga4.removeAll()` to avoid duplicate listeners in long-lived apps. ## License ISC