Skip to content

fix: prevent memory leaks in all Google Maps sub-components#651

Merged
harlan-zw merged 10 commits intomainfrom
fix/google-maps-memory-leak
Mar 19, 2026
Merged

fix: prevent memory leaks in all Google Maps sub-components#651
harlan-zw merged 10 commits intomainfrom
fix/google-maps-memory-leak

Conversation

@harlan-zw
Copy link
Collaborator

@harlan-zw harlan-zw commented Mar 19, 2026

Linked issue

Closes #646

Type of change

  • 📖 Documentation
  • 🐛 Bug fix
  • 🔧 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

Description

The previous fix (#649) only handled one race condition (unmounting during await importLibrary). The reporter confirmed the leak persisted in beta.25. The root cause was orphaned Vue watchers: every Google Maps sub-component created a nested whenever/watch inside an async callback. In Vue 3, watchers created after an await lose component instance context, so they're never auto-stopped on unmount — retaining the entire reactive scope indefinitely.

Changes:

  • New useGoogleMapsResource composable encoding lifecycle safety (unmount guards, post-unmount cleanup, automatic ref nulling)
  • All 10 sub-components refactored to use it
  • Parent ScriptGoogleMaps cleanup made synchronous (Vue doesn't await async hooks), caches cleared on unmount
  • let variables → shallowRef for proper GC
  • Injection keys extracted to injectionKeys.ts (re-exported for backwards compat)
  • InfoWindow click listeners on parent markers now properly removed on cleanup
  • Docs trimmed — replaced verbose best practices with component reference table

27 new tests covering composable lifecycle, actual SFC components, v-for list changes (#646 scenario), v-if toggling, deferred map readiness, options reactivity after unmount, nested trees, and mount/unmount cycles.

The previous fix (#649) only handled one race condition — unmounting during
`await importLibrary`. The actual leak was caused by orphaned Vue watchers:
watchers created after an `await` lose component instance context and are
never auto-stopped on unmount, retaining the entire reactive scope.

- Create `useGoogleMapsResource` composable encoding lifecycle safety
- Move all options watchers to synchronous setup scope (Vue auto-stops)
- Add unmount guards to all async resource creation callbacks
- Null all resource refs on unmount to release Google Maps objects
- Convert `let` variables to `shallowRef` for proper GC
- Make parent `onBeforeUnmount` synchronous (Vue doesn't await async hooks)
- Clear `libraries` and `queryToLatLngCache` on parent unmount
- Extract injection keys to shared `injectionKeys.ts`

Closes #646
@vercel
Copy link
Contributor

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scripts-playground Ready Ready Preview, Comment Mar 19, 2026 4:19am

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 19, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/scripts@651

commit: 2fa9ce5

Replace verbose AI-generated best practices, error handling, and reactive
data pattern sections with a concise component reference table and
hierarchy diagram.
The .vue component imports fail typecheck in CI because google.maps
types are not available in the vitest typecheck context.
@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request refactors the Google Maps component library to centralize lifecycle management patterns. A new useGoogleMapsResource composable is introduced to abstract common resource creation and cleanup logic previously implemented via @vueuse/core's whenever and onUnmounted patterns. Injection keys are extracted to a dedicated injectionKeys.ts module. Approximately 10 component files are refactored to use the new composable, standardizing how resources (markers, circles, polygons, polylines, rectangles, heatmaps, info windows, pin elements, and marker clusterers) are created, updated, and cleaned up. Promise handling in ScriptGoogleMaps.vue is simplified by removing explicit new Promise wrappers. Test coverage is added for the new composable and mock utilities. Documentation is condensed to highlight the unified component pattern.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing memory leaks in Google Maps sub-components. It directly summarizes the core objective of the PR.
Description check ✅ Passed The description covers the required sections: linked issue (Closes #646), type of change (Bug fix selected), and a detailed explanation of the root cause, changes made, and testing. All template sections are addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/google-maps-memory-leak
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/runtime/components/GoogleMaps/injectionKeys.ts (1)

8-14: Consider using distinct Symbol descriptions for debugging clarity.

Both ADVANCED_MARKER_ELEMENT_INJECTION_KEY (line 8) and MARKER_INJECTION_KEY (line 12) use Symbol('marker'). While functionally correct (each Symbol() call creates a unique symbol), using the same description string can make debugging harder when inspecting injection keys in Vue DevTools or error messages.

♻️ Suggested improvement
-export const ADVANCED_MARKER_ELEMENT_INJECTION_KEY = Symbol('marker') as InjectionKey<{
+export const ADVANCED_MARKER_ELEMENT_INJECTION_KEY = Symbol('advancedMarkerElement') as InjectionKey<{
   advancedMarkerElement: ShallowRef<google.maps.marker.AdvancedMarkerElement | undefined>
 }>

 export const MARKER_INJECTION_KEY = Symbol('marker') as InjectionKey<{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/GoogleMaps/injectionKeys.ts` around lines 8 - 14, The
two injection keys currently both call Symbol('marker'), which makes debugging
harder; update ADVANCED_MARKER_ELEMENT_INJECTION_KEY to use a distinct
description (e.g., Symbol('advanced-marker-element') or similar) and keep
MARKER_INJECTION_KEY with a clear description (e.g., Symbol('marker')), so
replace the Symbol('marker') in the ADVANCED_MARKER_ELEMENT_INJECTION_KEY
declaration with a unique description string to make the symbols distinguishable
in logs and devtools; ensure you only change the description text and keep the
InjectionKey types (advancedMarkerElement and marker ShallowRef types) intact.
src/runtime/components/GoogleMaps/useGoogleMapsResource.ts (1)

57-63: Consider wrapping cleanup in try-finally.

If cleanup throws an exception, resource.value would not be set to undefined, potentially preventing garbage collection. Wrapping in try-finally ensures the ref is always nulled.

🛡️ Proposed improvement
  onUnmounted(() => {
    isUnmounted.value = true
-   if (resource.value && cleanup && mapContext?.mapsApi.value) {
-     cleanup(resource.value, { mapsApi: mapContext.mapsApi.value })
+   try {
+     if (resource.value && cleanup && mapContext?.mapsApi.value) {
+       cleanup(resource.value, { mapsApi: mapContext.mapsApi.value })
+     }
+   }
+   finally {
+     resource.value = undefined
    }
-   resource.value = undefined
  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/GoogleMaps/useGoogleMapsResource.ts` around lines 57 -
63, In onUnmounted callback, ensure resource.value is always cleared even if
cleanup throws: wrap the cleanup(resource.value, { mapsApi:
mapContext.mapsApi.value }) call in a try-finally block so that resource.value =
undefined executes in the finally; keep the existing isUnmounted update and the
conditional (resource.value && cleanup && mapContext?.mapsApi.value) but perform
cleanup inside try and nulling of resource.value in finally to guarantee GC-safe
teardown.
test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts (1)

33-37: Consider adding a comment explaining the magic number.

The flushAsync(ticks = 4) helper uses a default of 4 ticks. Adding a brief comment explaining why 4 ticks are typically needed (e.g., for async resource creation to complete) would improve maintainability.

📝 Suggested improvement
+// Multiple ticks needed: watcher trigger → async create → promise resolve → ref assignment
 async function flushAsync(ticks = 4) {
   for (let i = 0; i < ticks; i++) {
     await nextTick()
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts` around lines 33 - 37,
The helper function flushAsync uses a default magic number of 4 ticks; add a
brief inline comment above flushAsync explaining why 4 ticks is chosen (e.g., to
allow multiple microtask/macrotask cycles for async resource/component creation
and event propagation to complete when testing lifecycle), referencing the
function name flushAsync and its use of nextTick so future readers understand
the required delay and can adjust the default if test timing changes.
src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue (1)

54-56: Inconsistent noDraw flag usage compared to regular Marker.

In ScriptGoogleMapsMarker.vue (line 54-55), addMarker is called with noDraw: true followed by requestRerender() for batch efficiency. Here, addMarker is called without the flag. Consider aligning the behavior for consistency and potential performance benefits with many markers.

♻️ Suggested alignment with ScriptGoogleMapsMarker
     if (markerClustererContext?.markerClusterer.value) {
-      markerClustererContext.markerClusterer.value.addMarker(marker)
+      markerClustererContext.markerClusterer.value.addMarker(marker, true)
+      markerClustererContext.requestRerender()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue`
around lines 54 - 56, The marker is added to the clusterer without the noDraw
optimization; change the call on
markerClustererContext.markerClusterer.value.addMarker to pass the noDraw flag
(e.g., addMarker(marker, { noDraw: true })) and then invoke the marker
clusterer's requestRerender()
(markerClustererContext.markerClusterer.value.requestRerender()) to match the
batching behavior used in ScriptGoogleMapsMarker.vue for performance consistency
when adding many markers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue`:
- Around line 62-70: Align the defensive checks in cleanup with create: update
the cleanup function (cleanup) to verify
markerClustererContext?.markerClusterer.value before calling
removeMarker(marker) so it mirrors the create() check; if that value is absent,
fall back to clearing marker.map as before. Ensure you reference
markerClustererContext, markerClusterer.value, removeMarker, and marker.map when
making the conditional so the else branch executes when the clusterer value is
undefined.

In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue`:
- Around line 41-67: The click listeners added to markerContext.marker.value and
advancedMarkerElementContext.advancedMarkerElement.value are never removed, so
update the resource to store the listener handles when calling addListener (or
store the parent marker reference) inside the function that creates the
InfoWindow (where iw is created) and then update cleanup(iw, { mapsApi }) to
remove those parent listeners using mapsApi.event.removeListener or
mapsApi.event.clearInstanceListeners on the stored marker/handle; adjust
references to infoWindow.value usage accordingly so the created listener handles
/ parent marker are accessible in cleanup (e.g., add a property on the resource
returned with markerListener or parentMarkerRef and remove it in cleanup).

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Line 39: The test suite title has a typo: the describe call uses 'scri
ptGoogleMapsAdvancedMarkerElement' with an extra space; update the describe
string to the correct identifier 'scriptGoogleMapsAdvancedMarkerElement' (i.e.,
remove the space) so the suite name is spelled correctly in the test for the
Google Maps lifecycle.

In `@test/unit/google-maps-lifecycle.test.ts`:
- Around line 1-5: Move the `@vitest-environment` directive to the very top of the
test file so Vitest recognizes it: place the line `/** `@vitest-environment`
happy-dom */` above all imports (i.e., before the `import { mount } from
'@vue/test-utils'` and other imports) in google-maps-lifecycle.test.ts so the
environment comment is the first thing in the file.

---

Nitpick comments:
In `@src/runtime/components/GoogleMaps/injectionKeys.ts`:
- Around line 8-14: The two injection keys currently both call Symbol('marker'),
which makes debugging harder; update ADVANCED_MARKER_ELEMENT_INJECTION_KEY to
use a distinct description (e.g., Symbol('advanced-marker-element') or similar)
and keep MARKER_INJECTION_KEY with a clear description (e.g., Symbol('marker')),
so replace the Symbol('marker') in the ADVANCED_MARKER_ELEMENT_INJECTION_KEY
declaration with a unique description string to make the symbols distinguishable
in logs and devtools; ensure you only change the description text and keep the
InjectionKey types (advancedMarkerElement and marker ShallowRef types) intact.

In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue`:
- Around line 54-56: The marker is added to the clusterer without the noDraw
optimization; change the call on
markerClustererContext.markerClusterer.value.addMarker to pass the noDraw flag
(e.g., addMarker(marker, { noDraw: true })) and then invoke the marker
clusterer's requestRerender()
(markerClustererContext.markerClusterer.value.requestRerender()) to match the
batching behavior used in ScriptGoogleMapsMarker.vue for performance consistency
when adding many markers.

In `@src/runtime/components/GoogleMaps/useGoogleMapsResource.ts`:
- Around line 57-63: In onUnmounted callback, ensure resource.value is always
cleared even if cleanup throws: wrap the cleanup(resource.value, { mapsApi:
mapContext.mapsApi.value }) call in a try-finally block so that resource.value =
undefined executes in the finally; keep the existing isUnmounted update and the
conditional (resource.value && cleanup && mapContext?.mapsApi.value) but perform
cleanup inside try and nulling of resource.value in finally to guarantee GC-safe
teardown.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Around line 33-37: The helper function flushAsync uses a default magic number
of 4 ticks; add a brief inline comment above flushAsync explaining why 4 ticks
is chosen (e.g., to allow multiple microtask/macrotask cycles for async
resource/component creation and event propagation to complete when testing
lifecycle), referencing the function name flushAsync and its use of nextTick so
future readers understand the required delay and can adjust the default if test
timing changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 96dd3ed2-caf9-4efd-80c3-613c4b6f6ee5

📥 Commits

Reviewing files that changed from the base of the PR and between 89a9ebe and 682d752.

📒 Files selected for processing (16)
  • src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue
  • src/runtime/components/GoogleMaps/injectionKeys.ts
  • src/runtime/components/GoogleMaps/useGoogleMapsResource.ts
  • test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts
  • test/unit/__mocks__/google-maps-api.ts
  • test/unit/google-maps-lifecycle.test.ts

Static .vue imports fail CI typecheck because the generated nuxt
tsconfig has empty types array — google.maps types aren't included.
Dynamic imports bypass the typecheck phase.
- Align clusterer cleanup check with create check in AdvancedMarkerElement
- Track and remove parent marker click listener in InfoWindow cleanup
- Fix typo in test describe block name
Use triple-slash reference directive to make google.maps ambient types
available to the nuxt-runtime test file, fixing the CI typecheck that
couldn't resolve .vue component module types.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
docs/content/scripts/google-maps.md (1)

537-537: Clarify “basic Google Maps object” wording.

“basic Google Maps object” is ambiguous; “underlying Google Maps object” is clearer and matches the implementation intent.

Suggested wording
-All SFC components accept an `options` prop matching their Google Maps API options type (excluding `map`, which is injected automatically). Options are reactive - changes update the basic Google Maps object. Components clean up automatically on unmount.
+All SFC components accept an `options` prop matching their Google Maps API options type (excluding `map`, injected automatically). Options are reactive—changes update the underlying Google Maps object. Components clean up automatically on unmount.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/scripts/google-maps.md` at line 537, Update the ambiguous phrase
"basic Google Maps object" to "underlying Google Maps object" in the sentence
describing SFC component behavior so it reads: "Options are reactive - changes
update the underlying Google Maps object." Search for the exact phrase "basic
Google Maps object" in the docs/content/scripts/google-maps.md and replace it
(and any exact duplicates nearby) to ensure the wording matches implementation
intent.
test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts (1)

41-45: Don’t rely on an arbitrary 4×nextTick() flush here.

These components create resources after awaited library imports. A fixed tick count couples the suite to the current scheduler depth and can make the lifecycle assertions flaky as soon as that async chain changes. Prefer flushing promises and then a Vue tick instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts` around lines 41 - 45,
The current flushAsync implementation in the test (function flushAsync using a
fixed 4× nextTick loop) is flaky; change it to first await promise microtasks to
drain pending library imports and then await a single Vue tick. Concretely,
replace the fixed loop in flushAsync with: await flushPromises() (import
flushPromises from 'flush-promises' or use awaiting Promise.resolve() repeatedly
until microtasks are drained) followed by await nextTick(); update any tests
that call flushAsync to use this new behavior. Ensure you keep the function name
flushAsync and the nextTick await so lifecycle assertions run after microtasks
and one Vue render tick.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/content/scripts/google-maps.md`:
- Line 529: Update the hierarchy line so component names are consistently
prefixed: replace the mixed "ScriptGoogleMapsMarker / AdvancedMarkerElement"
with a prefixed form such as "ScriptGoogleMapsAdvancedMarkerElement /
AdvancedMarkerElement" so readers won't mistake it for a different component
(reference the component name ScriptGoogleMapsAdvancedMarkerElement and the
child AdvancedMarkerElement when making the change).
- Around line 526-535: The fenced hierarchy block starting with
"ScriptGoogleMaps (root)" is missing a language tag which triggers markdownlint
MD040; update the opening fence from ``` to ```text so the block becomes a text
code fence (keep the content and closing ``` unchanged) to satisfy the linter.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Around line 580-611: The test currently unmounts the entire Provider (via
wrapper.unmount()) which destroys the marker instance and masks watcher leaks;
instead mount the Provider and render the Child conditionally with a v-if flag
(e.g., showChild ref) so you only unmount the
Child/ScriptGoogleMapsAdvancedMarkerElement, then mutate the same reactive
optionsRef object (e.g., change optionsRef.value.position.lat/lng) rather than
replacing it to trigger the watcher; after flipping showChild to false and
awaiting flushAsync(), change the optionsRef properties and assert the existing
marker instance did not get updated (verifying the watcher attached in the Child
was cleaned up). Ensure you reference Provider, Child, optionsRef,
ScriptGoogleMapsAdvancedMarkerElement, mountSuspended, and flushAsync when
locating the test code to modify.

---

Nitpick comments:
In `@docs/content/scripts/google-maps.md`:
- Line 537: Update the ambiguous phrase "basic Google Maps object" to
"underlying Google Maps object" in the sentence describing SFC component
behavior so it reads: "Options are reactive - changes update the underlying
Google Maps object." Search for the exact phrase "basic Google Maps object" in
the docs/content/scripts/google-maps.md and replace it (and any exact duplicates
nearby) to ensure the wording matches implementation intent.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Around line 41-45: The current flushAsync implementation in the test (function
flushAsync using a fixed 4× nextTick loop) is flaky; change it to first await
promise microtasks to drain pending library imports and then await a single Vue
tick. Concretely, replace the fixed loop in flushAsync with: await
flushPromises() (import flushPromises from 'flush-promises' or use awaiting
Promise.resolve() repeatedly until microtasks are drained) followed by await
nextTick(); update any tests that call flushAsync to use this new behavior.
Ensure you keep the function name flushAsync and the nextTick await so lifecycle
assertions run after microtasks and one Vue render tick.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f05daa3-02af-48c4-9fee-3ddca340926d

📥 Commits

Reviewing files that changed from the base of the PR and between 682d752 and c82f5ef.

📒 Files selected for processing (2)
  • docs/content/scripts/google-maps.md
  • test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts

- Improve options watcher test to unmount only child (not whole tree)
- Move @vitest-environment directive to top of file
- Add language tag to docs hierarchy code block
- Use full component names in hierarchy diagram
The nuxt-runtime vitest project's generated tsconfig cannot resolve
.vue module imports, causing TypeCheckError for component imports.
Runtime tests pass — typecheck is handled by the dedicated typecheck
project.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
docs/content/scripts/google-maps.md (1)

541-544: Consider noting the actual prop type in the Options Type column.

The actual component props use Omit<google.maps.MarkerOptions, 'map'> and Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'> (see ScriptGoogleMapsMarker.vue:11 and ScriptGoogleMapsAdvancedMarkerElement.vue:12), but the table shows the base types. While line 537 does clarify that map is excluded, explicitly showing Omit<..., 'map'> in the table would be more precise for developers checking type compatibility.

📖 Suggested table update
-| `ScriptGoogleMapsMarker` | `google.maps.MarkerOptions` | Classic marker |
-| `ScriptGoogleMapsAdvancedMarkerElement` | `google.maps.marker.AdvancedMarkerElementOptions` | Recommended |
+| `ScriptGoogleMapsMarker` | `Omit<google.maps.MarkerOptions, 'map'>` | Classic marker |
+| `ScriptGoogleMapsAdvancedMarkerElement` | `Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'>` | Recommended |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/scripts/google-maps.md` around lines 541 - 544, Update the
Options Type column to reflect the actual prop types used by the components:
change the entries for ScriptGoogleMapsMarker and
ScriptGoogleMapsAdvancedMarkerElement to use Omit types
(Omit<google.maps.MarkerOptions, 'map'> and
Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'> respectively) so
the table matches the prop definitions in ScriptGoogleMapsMarker (uses
Omit<google.maps.MarkerOptions, 'map'>) and
ScriptGoogleMapsAdvancedMarkerElement (uses
Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'>) and makes the
exclusion of 'map' explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Around line 6-13: Direct imports of .vue components cause TypeScript errors in
CI; replace the static imports of ScriptGoogleMapsAdvancedMarkerElement,
ScriptGoogleMapsCircle, ScriptGoogleMapsHeatmapLayer, ScriptGoogleMapsMarker,
ScriptGoogleMapsPinElement, ScriptGoogleMapsPolygon, ScriptGoogleMapsPolyline,
and ScriptGoogleMapsRectangle with dynamic imports wrapped in
Vue.defineAsyncComponent (i.e. import them via () => import('...') and pass to
defineAsyncComponent) and add defineAsyncComponent to the Vue import statement
so tests use async component resolution and avoid the missing type declarations
error.

---

Nitpick comments:
In `@docs/content/scripts/google-maps.md`:
- Around line 541-544: Update the Options Type column to reflect the actual prop
types used by the components: change the entries for ScriptGoogleMapsMarker and
ScriptGoogleMapsAdvancedMarkerElement to use Omit types
(Omit<google.maps.MarkerOptions, 'map'> and
Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'> respectively) so
the table matches the prop definitions in ScriptGoogleMapsMarker (uses
Omit<google.maps.MarkerOptions, 'map'>) and
ScriptGoogleMapsAdvancedMarkerElement (uses
Omit<google.maps.marker.AdvancedMarkerElementOptions, 'map'>) and makes the
exclusion of 'map' explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3501a7f9-4577-46dc-bdfc-50df5f084466

📥 Commits

Reviewing files that changed from the base of the PR and between c82f5ef and 485da9f.

📒 Files selected for processing (5)
  • docs/content/scripts/google-maps.md
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue
  • test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts
  • test/unit/google-maps-lifecycle.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test/unit/google-maps-lifecycle.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue

The root tsconfig included test/nuxt-runtime in its scope, causing
vue-tsc to attempt resolving .vue module imports during the typecheck
vitest project. Excluding it matches the existing pattern for test/unit.

Also moved SFC component tests back to composable-only tests in the
nuxt-runtime file (component tests stay in the unit file where
@vue/test-utils handles .vue parsing).
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts (1)

196-241: Consider consolidating with the earlier race condition test.

This test exercises the same code path as 'should handle async resource creation with unmount race condition' (lines 75-115) — both verify that resources created during an async gap after unmount are immediately cleaned up. The only difference is the framing ("import" delay vs "create" delay).

If the intent is to document multiple real-world scenarios, the current approach is valid. Otherwise, these could be consolidated into a single parameterized test to reduce redundancy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts` around lines 196 - 241,
The two tests 'should cleanup resources created during async gap after unmount'
and 'should handle async resource creation with unmount race condition' exercise
the same code path and should be consolidated to avoid redundancy; refactor the
tests into one parameterized case (or remove the duplicate) that covers both
"import" delay and "create" delay scenarios by reusing the same test logic
around createMapProvider/Provider, the Child component using
useGoogleMapsResource, and the cleanupFn assertion, or convert them into a
single test with a parameter controlling whether the delay is introduced in the
importPromise or inside create to verify cleanup behavior in both real-world
variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts`:
- Around line 196-241: The two tests 'should cleanup resources created during
async gap after unmount' and 'should handle async resource creation with unmount
race condition' exercise the same code path and should be consolidated to avoid
redundancy; refactor the tests into one parameterized case (or remove the
duplicate) that covers both "import" delay and "create" delay scenarios by
reusing the same test logic around createMapProvider/Provider, the Child
component using useGoogleMapsResource, and the cleanupFn assertion, or convert
them into a single test with a parameter controlling whether the delay is
introduced in the importPromise or inside create to verify cleanup behavior in
both real-world variants.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56d251d1-02eb-4420-9394-579c1fb1f57d

📥 Commits

Reviewing files that changed from the base of the PR and between 7dcea8f and 2fa9ce5.

📒 Files selected for processing (2)
  • test/nuxt-runtime/google-maps-lifecycle.nuxt.test.ts
  • tsconfig.json
✅ Files skipped from review due to trivial changes (1)
  • tsconfig.json

@harlan-zw harlan-zw merged commit 039380e into main Mar 19, 2026
10 checks passed
@harlan-zw harlan-zw deleted the fix/google-maps-memory-leak branch March 19, 2026 04:52
@harlan-zw harlan-zw mentioned this pull request Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cleanup causing a memory leak with <ScriptGoogleMapsAdvancedMarkerElement>

1 participant