Skip to content

feat(maps): add Leaflet and MapLibre integrations#833

Open
harlan-zw wants to merge 14 commits into
mainfrom
feat/leaflet
Open

feat(maps): add Leaflet and MapLibre integrations#833
harlan-zw wants to merge 14 commits into
mainfrom
feat/leaflet

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

No linked issue.

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Adds Leaflet 1.9.4 and MapLibre GL JS 5.24 as lazy-loaded, provider-agnostic alternatives to Google Maps.

Both integrations include Nuxt components for maps, markers, popups, and GeoJSON. Leaflet supports raster tile providers, while MapLibre adds vector styles, reactive camera controls, navigation controls, and CSP worker configuration.

Both load near the viewport and reserve layout space during SSR. Child resources are removed before the map is torn down.

The docs follow the existing Google Maps structure and cover setup, providers, accessibility, and real-world examples. The playground includes a Leaflet pickup locator using OpenStreetMap and a MapLibre delivery tracker using OpenFreeMap.

📸 Screenshots

Leaflet pickup locator

Leaflet pickup locator showing Melbourne locations, OpenStreetMap tiles, and the Queen Victoria Market popup

MapLibre delivery tracker

MapLibre delivery tracker showing route progress from West Melbourne to Docklands using OpenFreeMap

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
scripts-playground Ready Ready Preview, Comment Jul 21, 2026 2:53pm

Request Review

@socket-security

socket-security Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmaplibre-gl@​5.24.0981001009970
Added@​types/​leaflet@​1.9.211001007781100

View full report

@pkg-pr-new

pkg-pr-new Bot commented Jul 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: eb0d813

@harlan-zw

Copy link
Copy Markdown
Collaborator Author

CI note: both test jobs now fail only the two existing jQuery bundling snapshots in test/e2e/base.test.ts and test/e2e/basic.test.ts. The same snapshots fail on main in run 29690218584. The Leaflet-specific Unhead v3 typecheck failure from the first run was fixed in e3ca96c7; both current typecheck jobs pass.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

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

Adds Leaflet and MapLibre GL JS integrations to Nuxt Scripts with lazy loading, bundled styling, typed registry metadata, declarative map components, resource lifecycle management, tests, licensing, documentation, and playground examples.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: adding Leaflet and MapLibre map integrations.
Description check ✅ Passed The description is directly about the Leaflet and MapLibre integration work and matches the changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/leaflet

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
packages/script/src/runtime/components/Leaflet/useLeafletResource.ts (1)

23-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

bindLeafletEvents's any-typed emit forces unsafe casts at every call site. The shared helper's signature (target, emit: (event: any, ...args: any[]) => void, events) discards the type safety of each component's defineEmits, which is why every caller must cast emit as any.

  • packages/script/src/runtime/components/Leaflet/useLeafletResource.ts#L23-L30: make bindLeafletEvents generic over an event-map type (e.g. <E extends Record<string, any[]>>(target, emit: <K extends keyof E & string>(event: K, ...args: E[K]) => void, events: readonly (keyof E & string)[])) so it can accept a component's actual emit function without casting.
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue#L28-L28: drop the emit as any cast once the helper is generic.
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue#L48-L48: drop the emit as any cast once the helper is generic.
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue#L30-L30: drop the emit as any cast once the helper is generic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/script/src/runtime/components/Leaflet/useLeafletResource.ts` around
lines 23 - 30, Make bindLeafletEvents generic over an event-map type so its emit
callback and events list use the component’s typed event names and argument
tuples instead of any. In
packages/script/src/runtime/components/Leaflet/useLeafletResource.ts:23-30,
update bindLeafletEvents accordingly; in
packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue:28,
packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue:48, and
packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue:30,
remove the emit as any casts and pass each component’s typed emit directly.
packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue (1)

143-148: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Verify whether the underlying script loader exposes richer error detail than this generic message.

watch(status, ...) fabricates a fresh, generic Error('Leaflet failed to load') rather than surfacing whatever useScriptLeaflet/useScript actually reports on failure. Unhead's useScript() exposes a dedicated onError callback for this purpose — worth confirming whether using it here would let you forward a more specific/original error instead of overwriting it, since the underlying composable's exact error signature isn't visible in this file set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue` around
lines 143 - 148, Update the Leaflet script error handling around
useScriptLeaflet and the status watcher to use the loader’s onError callback or
exposed error value when available, forwarding the original/richer error through
loadError and emit('error'). Avoid replacing the underlying failure with the
generic Error('Leaflet failed to load'); preserve a fallback only when no loader
error detail is provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue`:
- Line 24: Update the dragstart event type in the Leaflet marker event
definitions to use LeafletEvent instead of Leaflet.DragEndEvent. Keep
Leaflet.DragEndEvent for dragend, where distance is available.

---

Nitpick comments:
In `@packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue`:
- Around line 143-148: Update the Leaflet script error handling around
useScriptLeaflet and the status watcher to use the loader’s onError callback or
exposed error value when available, forwarding the original/richer error through
loadError and emit('error'). Avoid replacing the underlying failure with the
generic Error('Leaflet failed to load'); preserve a fallback only when no loader
error detail is provided.

In `@packages/script/src/runtime/components/Leaflet/useLeafletResource.ts`:
- Around line 23-30: Make bindLeafletEvents generic over an event-map type so
its emit callback and events list use the component’s typed event names and
argument tuples instead of any. In
packages/script/src/runtime/components/Leaflet/useLeafletResource.ts:23-30,
update bindLeafletEvents accordingly; in
packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue:28,
packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue:48, and
packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue:30,
remove the emit as any casts and pass each component’s typed emit directly.
🪄 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: 8728dada-c052-428e-8a69-59f1c7941cab

📥 Commits

Reviewing files that changed from the base of the PR and between 343573d and 4199e6b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • docs/content/scripts/leaflet/2.api/1.script-leaflet-map.md
  • docs/content/scripts/leaflet/2.api/2.tile-layer.md
  • docs/content/scripts/leaflet/2.api/3.marker.md
  • docs/content/scripts/leaflet/2.api/4.popup.md
  • docs/content/scripts/leaflet/2.api/5.geojson.md
  • docs/content/scripts/leaflet/2.api/6.use-script-leaflet.md
  • docs/content/scripts/leaflet/index.md
  • package.json
  • packages/script/THIRD_PARTY_LICENSES.md
  • packages/script/package.json
  • packages/script/src/registry-logos.ts
  • packages/script/src/registry-types.json
  • packages/script/src/registry.ts
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletPopup.vue
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue
  • packages/script/src/runtime/components/Leaflet/useLeafletResource.ts
  • packages/script/src/runtime/leaflet-styles.ts
  • packages/script/src/runtime/registry/leaflet.ts
  • packages/script/src/runtime/registry/schemas.ts
  • packages/script/src/runtime/types.ts
  • packages/script/src/script-meta.ts
  • playground/nuxt.config.ts
  • playground/pages/index.vue
  • playground/pages/third-parties/leaflet.vue
  • pnpm-workspace.yaml
  • scripts/generate-registry-types.ts
  • test/nuxt-runtime/leaflet-components.nuxt.test.ts
  • test/nuxt-runtime/leaflet-map.nuxt.test.ts
  • test/types/types.test-d.ts
  • test/unit/leaflet-lifecycle.test.ts
  • test/unit/leaflet-registry.test.ts
  • test/unit/leaflet-styles.test.ts

Comment thread packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue Outdated
Comment thread scripts/generate-registry-types.ts Fixed
@socket-security

socket-security Bot commented Jul 20, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm gl-matrix is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/maplibre-gl@5.24.0npm/gl-matrix@3.4.4

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/gl-matrix@3.4.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@harlan-zw harlan-zw changed the title feat(leaflet): add Leaflet map integration feat(maps): add Leaflet and MapLibre integrations Jul 20, 2026
Comment thread scripts/generate-registry-types.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue (1)

83-91: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reactive options updates only take effect for maxWidth/offset; other fields are silently ignored.

Object.assign(popup.value.options, options) (Line 86) merges into the popup's internal options object, but only maxWidth and offset are re-applied via their public setters afterward. Fields like className, closeButton, closeOnClick, or anchor are mutated in the internal bag but have no setter invoked, so changing them reactively has no visible effect — inconsistent with the apparent full reactivity of the options prop.

Consider calling the relevant public methods for fields that have one (e.g. addClassName/removeClassName for className), or documenting that only maxWidth/offset are supported for reactive updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue`
around lines 83 - 91, Update the options watcher in the MapLibre popup component
so every supported reactive option is applied through its corresponding public
popup API, rather than only mutating popup.value.options; specifically handle
fields such as className, closeButton, closeOnClick, and anchor using their
available methods, while preserving the existing maxWidth and offset updates. Do
not silently claim full reactivity for fields that lack a public update API;
document or explicitly limit those fields if they cannot be applied.
scripts/generate-registry-types.ts (1)

382-393: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider logging when a TSTypeReference can't be resolved.

If namedTypes.get(typeNode.typeName.name) misses (e.g., a future Leaflet/MapLibre-style component imports its Props/Events/Slots type from another file instead of declaring it in the SFC's own normal <script> block), resolveTypeNode silently falls back to the unresolved reference, and downstream extraction quietly produces empty fields/events/slots with no error — the generated docs would silently regress with no signal to catch it in CI.

♻️ Suggested defensive logging
     const declaration = namedTypes.get(typeNode.typeName.name)
-    if (!declaration)
+    if (!declaration) {
+      console.warn(`[generate-registry-types] Could not resolve type "${typeNode.typeName.name}" in ${fileName}; declare it in the component's normal <script> block.`)
       return { node: typeNode, source: scriptSource }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-registry-types.ts` around lines 382 - 393, Add defensive
logging in resolveTypeNode when a TSTypeReference with an Identifier typeName
has no matching declaration from namedTypes.get. Include the unresolved type
name and enough context to identify the source, then preserve the existing
fallback return so downstream behavior remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/script/src/runtime/components/MapLibre/ScriptMapLibreGeoJson.vue`:
- Around line 34-89: Make sourceId changes reactive in the resource
synchronization flow: update the watcher around props.layers,
props.sourceOptions, and props.beforeId to include props.sourceId, ensuring the
previous source and owned layers are removed before recreating resources with
the new ID. Update removeOwnedResources or the surrounding sync lifecycle to
retain and remove the actually-created source ID during cleanup, including
unmount cleanup.

In `@packages/script/src/runtime/components/MapLibre/ScriptMapLibreMarker.vue`:
- Around line 82-93: Update the watcher for props.options to call setDraggable,
setRotation, setRotationAlignment, and setPitchAlignment only when their
corresponding option fields are defined, preserving existing marker state during
partial updates. Replace the direct element style opacity assignment with
marker.value.setOpacity(options.opacity, options.opacityWhenCovered), while
retaining the existing opacity guard.

In `@packages/script/src/runtime/maplibre-styles.ts`:
- Around line 7-23: Update ensureMapLibreStyles so deduplication compares the
requested stylesheetUrl with the URL already injected, rather than returning
solely when MAPLIBRE_STYLE_ID exists. Allow a different URL to create its own
stylesheet link while continuing to avoid duplicate injections of the same URL;
preserve the existing integrity and crossOrigin handling for the default URL.

In `@playground/pages/third-parties/maplibre.vue`:
- Line 5: Relax the center ref type used with ScriptMapLibreMap so it accepts
the component’s emitted MapLibre.LngLat camera-state object instead of only a
numeric tuple. Update both playground/pages/third-parties/maplibre.vue:5-5 and
docs/content/scripts/maplibre/index.md:46-46, using a compatible broad type such
as MapLibre.LngLatLike or any.
- Line 114: Add role="group" to the div with class "controls" in the map camera
controls markup so its existing aria-label is announced by screen readers.

---

Nitpick comments:
In `@packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue`:
- Around line 83-91: Update the options watcher in the MapLibre popup component
so every supported reactive option is applied through its corresponding public
popup API, rather than only mutating popup.value.options; specifically handle
fields such as className, closeButton, closeOnClick, and anchor using their
available methods, while preserving the existing maxWidth and offset updates. Do
not silently claim full reactivity for fields that lack a public update API;
document or explicitly limit those fields if they cannot be applied.

In `@scripts/generate-registry-types.ts`:
- Around line 382-393: Add defensive logging in resolveTypeNode when a
TSTypeReference with an Identifier typeName has no matching declaration from
namedTypes.get. Include the unresolved type name and enough context to identify
the source, then preserve the existing fallback return so downstream behavior
remains unchanged.
🪄 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: 0d3fcdcf-6ef1-47c7-b77f-691380fde560

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9daa2 and 789b22b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • docs/content/scripts/maplibre/2.api/1.script-maplibre-map.md
  • docs/content/scripts/maplibre/2.api/2.marker.md
  • docs/content/scripts/maplibre/2.api/3.popup.md
  • docs/content/scripts/maplibre/2.api/4.navigation-control.md
  • docs/content/scripts/maplibre/2.api/5.geojson.md
  • docs/content/scripts/maplibre/2.api/6.use-script-maplibre.md
  • docs/content/scripts/maplibre/index.md
  • package.json
  • packages/script/THIRD_PARTY_LICENSES.md
  • packages/script/package.json
  • packages/script/src/registry-logos.ts
  • packages/script/src/registry-types.json
  • packages/script/src/registry.ts
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue
  • packages/script/src/runtime/components/MapLibre/ScriptMapLibreGeoJson.vue
  • packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue
  • packages/script/src/runtime/components/MapLibre/ScriptMapLibreMarker.vue
  • packages/script/src/runtime/components/MapLibre/ScriptMapLibreNavigationControl.vue
  • packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue
  • packages/script/src/runtime/components/MapLibre/useMapLibreResource.ts
  • packages/script/src/runtime/maplibre-styles.ts
  • packages/script/src/runtime/registry/maplibre.ts
  • packages/script/src/runtime/registry/schemas.ts
  • packages/script/src/runtime/types.ts
  • packages/script/src/script-meta.ts
  • playground/nuxt.config.ts
  • playground/pages/index.vue
  • playground/pages/third-parties/maplibre.vue
  • pnpm-workspace.yaml
  • scripts/generate-registry-types.ts
  • test/nuxt-runtime/maplibre-components.nuxt.test.ts
  • test/nuxt-runtime/maplibre-map.nuxt.test.ts
  • test/types/types.test-d.ts
  • test/unit/maplibre-lifecycle.test.ts
  • test/unit/maplibre-registry.test.ts
  • test/unit/maplibre-styles.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • playground/nuxt.config.ts
  • pnpm-workspace.yaml
  • test/types/types.test-d.ts
  • playground/pages/index.vue
  • packages/script/src/script-meta.ts
  • package.json
  • packages/script/src/runtime/types.ts
  • packages/script/package.json
  • packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue

Comment thread packages/script/src/runtime/maplibre-styles.ts
Comment thread playground/pages/third-parties/maplibre.vue Outdated
Comment thread playground/pages/third-parties/maplibre.vue Outdated
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.

2 participants