diff --git a/packages/script/src/runtime/registry/google-tag-manager.ts b/packages/script/src/runtime/registry/google-tag-manager.ts index 98a6a967..81a16c84 100644 --- a/packages/script/src/runtime/registry/google-tag-manager.ts +++ b/packages/script/src/runtime/registry/google-tag-manager.ts @@ -72,9 +72,17 @@ export interface GoogleTagManagerApi { /** * Enhanced window type with GTM + * + * `dataLayer` is deliberately NOT declared globally. Its name is configurable through the + * `l` / `dataLayer` options, so `window.dataLayer` is not guaranteed to exist, and declaring it + * here makes this package irreconcilable with any other package that declares `Window.dataLayer` + * (for example `@gtm-support/core`, used by `@gtm-support/vue-gtm`): the two declarations cannot + * merge, so consumers of both get an unsuppressable TS2430 at every one of their own `Window` + * augmentations. Read it through the typed proxy returned by `useScriptGoogleTagManager()` + * instead, which is also the only access path that respects a custom dataLayer name. */ declare global { - interface Window extends GoogleTagManagerApi {} + interface Window extends Pick {} } export { GoogleTagManagerOptions } diff --git a/test/types/global-window.test-d.ts b/test/types/global-window.test-d.ts new file mode 100644 index 00000000..c940e36f --- /dev/null +++ b/test/types/global-window.test-d.ts @@ -0,0 +1,31 @@ +import type { GoogleTagManagerApi } from '../../packages/script/src/runtime/registry/google-tag-manager' +import { describe, expectTypeOf, it } from 'vitest' + +/** + * Regression guard for #852. + * + * `@gtm-support/core` (the engine behind `@gtm-support/vue-gtm`) declares + * `Window.dataLayer?: DataLayerObject[]`. Interface declarations merge, so if this package + * also declares `dataLayer` on the global `Window`, the merged `Window` fails this package's + * own `extends` clause. TypeScript then reports TS2430 at every one of the consumer's own + * `Window` augmentations, which the consumer cannot suppress. + * + * `dataLayer` must stay off the global `Window`. Its name is configurable through the + * `l` / `dataLayer` options anyway, so `window.dataLayer` was never guaranteed to exist. + * The proxy returned by `useScriptGoogleTagManager()` is the supported access path. + */ +describe('global `Window` augmentation', () => { + it('does not declare `dataLayer`', () => { + expectTypeOf<'dataLayer' extends keyof Window ? true : false>().toEqualTypeOf() + }) + + it('keeps `google_tag_manager` declared', () => { + // `useScriptGoogleTagManager`'s `use()` reads `window.google_tag_manager` directly. + expectTypeOf().toEqualTypeOf() + }) + + it('still types `dataLayer` on the GTM proxy API', () => { + expectTypeOf().not.toBeAny() + expectTypeOf().toBeCallableWith({ event: 'test' }) + }) +})