Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/content/docs/4.migration-guide/1.v0-to-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Third-party scripts expose data that enables fingerprinting users across sites. Every request shares the user's IP address, and scripts can set third-party cookies for cross-site tracking.

First-party mode acts as a reverse proxy: scripts are bundled at build time and served from your domain, while runtime collection requests are forwarded through Nitro server routes with automatic anonymisation. It is **auto-enabled** for scripts that support it, zero-config:

Check warning on line 14 in docs/content/docs/4.migration-guide/1.v0-to-v1.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "are bundled". Consider rewriting in active voice

Check warning on line 14 in docs/content/docs/4.migration-guide/1.v0-to-v1.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "are bundled". Consider rewriting in active voice

```ts
export default defineNuxtConfig({
Expand Down Expand Up @@ -220,7 +220,7 @@
})
```

If you only need infrastructure without loading the script on the page, set `trigger: false` explicitly. This registers proxy routes, TypeScript types, and bundling config, but no `<script>`{lang="html"} tag is injected. Useful when you load the script yourself via a component or composable.

Check warning on line 223 in docs/content/docs/4.migration-guide/1.v0-to-v1.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is injected". Consider rewriting in active voice

Check warning on line 223 in docs/content/docs/4.migration-guide/1.v0-to-v1.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is injected". Consider rewriting in active voice

```ts [nuxt.config.ts]
export default defineNuxtConfig({
Expand Down Expand Up @@ -392,6 +392,28 @@
+<ScriptGoogleMaps :map-options="{ center: { lat, lng }, zoom: 12 }" />
```

#### Template Ref `googleMaps` Renamed to `mapsApi`

The `googleMaps` template-ref key on `<ScriptGoogleMaps>`{lang="html"} has been renamed to `mapsApi` to better reflect what it holds (the `google.maps` API namespace, not the component itself). The old key still works as a deprecated alias and emits a one-shot dev-mode warning when read.

```diff
const mapRef = ref()
onMounted(() => {
- console.log(mapRef.value?.googleMaps)
+ console.log(mapRef.value?.mapsApi)
})
```

The same rename applies to `<ScriptGoogleMapsOverlayView>`{lang="html"}: its exposed `overlay` key is now `overlayView`, with `overlay` kept as a deprecated alias.

```diff
const overlayRef = ref()
onMounted(() => {
- console.log(overlayRef.value?.overlay)
+ console.log(overlayRef.value?.overlayView)
})
```

### Google Maps Static Placeholder ([#673](https://github.com/nuxt/scripts/pull/673))

v1 extracts the built-in static map placeholder into a standalone [`<ScriptGoogleMapsStaticMap>`{lang="html"}](/scripts/google-maps/api/static-map) component. This removes the following props from `<ScriptGoogleMaps>`{lang="html"}:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Access the basic Google Maps instances via a template ref. The exposed object co

| Property | Type | Description |
|----------|------|-------------|
| `googleMaps` | `Ref<typeof google.maps \| undefined>`{lang="html"} | The core Maps API library. |
| `mapsApi` | `ShallowRef<typeof google.maps \| undefined>`{lang="html"} | The core Maps API namespace (`google.maps`). |
| `googleMaps` | `ShallowRef<typeof google.maps \| undefined>`{lang="html"} | **Deprecated.** Alias for `mapsApi`; emits a dev-mode warning. Slated for removal in a future major version. |
| `map` | `ShallowRef<google.maps.Map>`{lang="html"} | The map instance. |
| `resolveQueryToLatLng` | `(query) => Promise<LatLngLiteral>`{lang="html"} | Geocode an address to coordinates. |
| `importLibrary` | `(name) => Promise<Library>`{lang="html"} | Load additional Google Maps libraries at runtime. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,17 @@ export interface ScriptGoogleMapsProps {

export interface ScriptGoogleMapsExpose {
/**
* A reference to the loaded Google Maps API, or `undefined` if not yet loaded.
* A reference to the loaded Google Maps API namespace (`google.maps`), or
* `undefined` if not yet loaded.
*/
mapsApi: ShallowRef<typeof google.maps | undefined>
/**
* A reference to the loaded Google Maps API namespace, or `undefined` if not
* yet loaded.
*
* @deprecated Use `mapsApi` instead. The `googleMaps` alias will be removed
* in a future major version.
* @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1
*/
googleMaps: ShallowRef<typeof google.maps | undefined>
/**
Expand Down Expand Up @@ -147,7 +157,7 @@ import { defu } from 'defu'
import { tryUseNuxtApp, useHead, useRuntimeConfig } from 'nuxt/app'
import { computed, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, useAttrs, useTemplateRef, watch } from 'vue'
import ScriptAriaLoadingIndicator from '../ScriptAriaLoadingIndicator.vue'
import { MAP_INJECTION_KEY, waitForMapsReady, warnDeprecatedTopLevelMapProps } from './useGoogleMapsResource'
import { defineDeprecatedAlias, MAP_INJECTION_KEY, waitForMapsReady, warnDeprecatedTopLevelMapProps } from './useGoogleMapsResource'

const props = withDefaults(defineProps<ScriptGoogleMapsProps>(), {
// @ts-expect-error untyped
Expand Down Expand Up @@ -315,14 +325,27 @@ function importLibrary<T>(key: string): Promise<T> {
return cached as Promise<T>
}

const googleMaps: ScriptGoogleMapsExpose = {
const exposed: ScriptGoogleMapsExpose = {
mapsApi,
// Plain alias for production. In dev, replaced below with a getter that
// emits a one-shot deprecation warning. Both forms return the same
// shallow ref as `mapsApi`.
googleMaps: mapsApi,
map,
resolveQueryToLatLng,
importLibrary,
}

defineExpose<ScriptGoogleMapsExpose>(googleMaps)
if (import.meta.dev) {
defineDeprecatedAlias(
exposed,
'googleMaps',
'mapsApi',
'[nuxt-scripts] <ScriptGoogleMaps> expose key "googleMaps" is deprecated; use "mapsApi" instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
)
}

defineExpose<ScriptGoogleMapsExpose>(exposed)

// Shared InfoWindow group: only one InfoWindow open at a time within this map
let activeInfoWindow: google.maps.InfoWindow | undefined
Expand All @@ -340,7 +363,7 @@ provide(MAP_INJECTION_KEY, {
onMounted(() => {
watch(isMapReady, (v) => {
if (v) {
emits('ready', googleMaps)
emits('ready', exposed)
}
})
watch(status, (v) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export interface ScriptGoogleMapsOverlayViewSlots {

export interface ScriptGoogleMapsOverlayViewExpose {
/** The underlying `OverlayView` instance. */
overlayView: ShallowRef<google.maps.OverlayView | undefined>
/**
* The underlying `OverlayView` instance.
*
* @deprecated Use `overlayView` instead. The `overlay` alias will be
* removed in a future major version.
* @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1
*/
overlay: ShallowRef<google.maps.OverlayView | undefined>
/** The current data-state of the overlay, either 'open' or 'closed'. */
dataState: Readonly<ShallowRef<'open' | 'closed'>>
Expand All @@ -72,7 +80,7 @@ export interface ScriptGoogleMapsOverlayViewExpose {
<script setup lang="ts">
import { computed, inject, ref, useTemplateRef, watch } from 'vue'
import { MARKER_CLUSTERER_INJECTION_KEY } from './types'
import { MARKER_INJECTION_KEY, useGoogleMapsResource } from './useGoogleMapsResource'
import { defineDeprecatedAlias, MARKER_INJECTION_KEY, useGoogleMapsResource } from './useGoogleMapsResource'

defineOptions({
inheritAttrs: false,
Expand Down Expand Up @@ -327,7 +335,25 @@ if (markerClustererContext && markerContext) {
)
}

defineExpose<ScriptGoogleMapsOverlayViewExpose>({ overlay, dataState })
const exposed: ScriptGoogleMapsOverlayViewExpose = {
overlayView: overlay,
// Plain alias for production. In dev, replaced below with a getter that
// emits a one-shot deprecation warning. Both forms return the same
// shallow ref as `overlayView`.
overlay,
dataState,
}

if (import.meta.dev) {
defineDeprecatedAlias(
exposed,
'overlay',
'overlayView',
'[nuxt-scripts] <ScriptGoogleMapsOverlayView> expose key "overlay" is deprecated; use "overlayView" instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
)
}

defineExpose<ScriptGoogleMapsOverlayViewExpose>(exposed)
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,37 @@ export interface GoogleMapsResourceContext {
mapsApi: typeof google.maps
}

/**
* Defines a deprecated property alias on an exposed object. Reading the alias
* returns the value of the canonical key and emits a one-shot
* `console.warn` (so repeated reads don't spam the console).
*
* Used to provide backward-compatible renames on `defineExpose` payloads
* without breaking existing template-ref consumers. Call sites should wrap
* this in `if (import.meta.dev)` so production builds skip the getter
* entirely and the alias stays a plain data property.
*/
export function defineDeprecatedAlias<T extends object, K extends keyof T>(
target: T,
alias: string,
canonicalKey: K,
message: string,
): T {
let warned = false
Object.defineProperty(target, alias, {
get() {
if (!warned) {
warned = true
console.warn(message)
}
return target[canonicalKey]
},
enumerable: true,
configurable: true,
})
return target
}

/**
* Emits dev-mode deprecation warnings for the legacy top-level `center` and
* `zoom` props on `<ScriptGoogleMaps>`. Both props still work, but new code
Expand Down
142 changes: 140 additions & 2 deletions test/unit/google-maps-components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { MocksType } from './__helpers__/google-maps-test-utils'
*/
import { defu } from 'defu'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ref } from 'vue'
import { warnDeprecatedTopLevelMapProps } from '../../packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource'
import { ref, shallowRef } from 'vue'
import { defineDeprecatedAlias, warnDeprecatedTopLevelMapProps } from '../../packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource'
import {

simulateAdvancedMarkerLifecycle,
Expand Down Expand Up @@ -507,3 +507,141 @@ describe('scriptGoogleMaps top-level center/zoom deprecation', () => {
})
})
})

describe('defineDeprecatedAlias', () => {
// Backs the `googleMaps` β†’ `mapsApi` and `overlay` β†’ `overlayView` renames
// on the exposed objects of `<ScriptGoogleMaps>` and
// `<ScriptGoogleMapsOverlayView>`. Reading the alias must:
// - return the same value as the canonical key (so existing call sites
// keep working)
// - emit a one-shot dev-mode console.warn (no spam on repeated reads)

let warnSpy: ReturnType<typeof vi.spyOn>

beforeEach(() => {
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
})

afterEach(() => {
warnSpy.mockRestore()
})

describe('googleMaps alias for mapsApi', () => {
function createMapExposed() {
const mapsApi = shallowRef<any>({ Marker: class {} })
const exposed: any = {
mapsApi,
googleMaps: mapsApi,
}
defineDeprecatedAlias(
exposed,
'googleMaps',
'mapsApi',
'[nuxt-scripts] <ScriptGoogleMaps> expose key "googleMaps" is deprecated; use "mapsApi" instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
)
return { exposed, mapsApi }
}

it('returns the same shallow ref as mapsApi', () => {
const { exposed, mapsApi } = createMapExposed()

expect(exposed.googleMaps).toBe(mapsApi)
expect(exposed.googleMaps).toBe(exposed.mapsApi)
})

it('fires a dev warning on first read', () => {
const { exposed } = createMapExposed()

// Touch the alias
void exposed.googleMaps

expect(warnSpy).toHaveBeenCalledTimes(1)
expect(warnSpy.mock.calls[0]![0]).toContain('"googleMaps" is deprecated')
expect(warnSpy.mock.calls[0]![0]).toContain('mapsApi')
expect(warnSpy.mock.calls[0]![0]).toContain('migration-guide/v0-to-v1')
})

it('does not spam the warning on repeated reads', () => {
const { exposed } = createMapExposed()

void exposed.googleMaps
void exposed.googleMaps
void exposed.googleMaps

expect(warnSpy).toHaveBeenCalledTimes(1)
})

it('reading the canonical mapsApi key does not warn', () => {
const { exposed } = createMapExposed()

void exposed.mapsApi
void exposed.mapsApi

expect(warnSpy).not.toHaveBeenCalled()
})

it('alias points to live ref (updates with mapsApi changes)', () => {
const { exposed, mapsApi } = createMapExposed()

// Both keys should reflect the new value via the underlying ShallowRef
mapsApi.value = { newValue: true }

expect(exposed.mapsApi.value).toEqual({ newValue: true })
expect(exposed.googleMaps.value).toEqual({ newValue: true })
})
})

describe('overlay alias for overlayView', () => {
function createOverlayExposed() {
const overlayView = shallowRef<any>({ setMap: vi.fn() })
const exposed: any = {
overlayView,
overlay: overlayView,
dataState: shallowRef<'open' | 'closed'>('closed'),
}
defineDeprecatedAlias(
exposed,
'overlay',
'overlayView',
'[nuxt-scripts] <ScriptGoogleMapsOverlayView> expose key "overlay" is deprecated; use "overlayView" instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
)
return { exposed, overlayView }
}

it('returns the same shallow ref as overlayView', () => {
const { exposed, overlayView } = createOverlayExposed()

expect(exposed.overlay).toBe(overlayView)
expect(exposed.overlay).toBe(exposed.overlayView)
})

it('fires a dev warning on first read', () => {
const { exposed } = createOverlayExposed()

void exposed.overlay

expect(warnSpy).toHaveBeenCalledTimes(1)
expect(warnSpy.mock.calls[0]![0]).toContain('"overlay" is deprecated')
expect(warnSpy.mock.calls[0]![0]).toContain('overlayView')
})

it('does not spam the warning on repeated reads', () => {
const { exposed } = createOverlayExposed()

void exposed.overlay
void exposed.overlay
void exposed.overlay

expect(warnSpy).toHaveBeenCalledTimes(1)
})

it('reading the canonical overlayView key does not warn', () => {
const { exposed } = createOverlayExposed()

void exposed.overlayView
void exposed.dataState

expect(warnSpy).not.toHaveBeenCalled()
})
})
})
Loading