Skip to content

Commit 5ece909

Browse files
authored
feat(maplibre)!: require MapLibre GL JS v6 (#866)
1 parent 7c8e629 commit 5ece909

13 files changed

Lines changed: 169 additions & 172 deletions

File tree

‎docs/content/scripts/maplibre/1.guides/3.performance-csp-and-accessibility.md‎

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ For a decorative map, set `:interactive="false"`. The component disables input,
7575

7676
## Stylesheet Loading
7777

78-
Nuxt Scripts injects its version-pinned MapLibre stylesheet when the SDK starts loading. To bundle the stylesheet with your app instead:
78+
Nuxt Scripts loads the stylesheet from the `maplibre-gl` package when the SDK starts loading. To control the stylesheet through Nuxt instead:
7979

8080
```ts [nuxt.config.ts]
8181
exportdefaultdefineNuxtConfig({
@@ -93,27 +93,15 @@ export default defineNuxtConfig({
9393

9494
## Content Security Policy
9595

96-
The standard MapLibre build creates a Blob worker. Its documented CSP includes `worker-src blob:`, `child-src blob:`, and`img-src data: blob:` alongside the origins used by your style.
96+
MapLibre v6 loads its worker from a real URL served by your own build, so `worker-src blob:` is no longer required. The map still needs`img-src data: blob:` alongside the origins used by your style.
9797

98-
For a policy that does not allow Blob workers, self-host MapLibre's CSP build and worker:
99-
100-
```ts [nuxt.config.ts]
101-
exportdefaultdefineNuxtConfig({
102-
scripts: {
103-
registry: {
104-
maplibre: {
105-
scriptInput: { src: '/maplibre-gl-csp.js' },
106-
},
107-
},
108-
},
109-
})
110-
```
98+
To serve the worker from a different path, set `worker-url`:
11199

112100
```vue
113101
<ScriptMapLibreMap
114102
:center="[144.9631, -37.8136]"
115103
map-style="/maps/style.json"
116-
worker-url="/maplibre-gl-csp-worker.js"
104+
worker-url="/maplibre-gl-worker.mjs"
117105
/>
118106
```
119107

‎docs/content/scripts/maplibre/index.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ links:
1414

1515
[MapLibre GL JS](https://maplibre.org/maplibre-gl-js/docs/) is an open-source WebGL renderer for interactive vector maps. It renders the map and handles interaction; you choose the style, tile source, attribution, geocoding, and routing services separately.
1616

17-
Nuxt Scripts supports MapLibre GL JS 5.24 through the [`useScriptMapLibre()`{lang="ts"}](/scripts/maplibre/api/use-script-maplibre) composable and declarative components for common map resources. First-party mode can bundle the JavaScript SDK. Nuxt Scripts loads the required MapLibre stylesheet separately by default.
17+
Nuxt Scripts supports MapLibre GL JS v6 through the [`useScriptMapLibre()`{lang="ts"}](/scripts/maplibre/api/use-script-maplibre) composable and declarative components for common map resources. MapLibre v6 is ESM-only, so Nuxt Scripts loads it from the `maplibre-gl` package rather than a CDN script tag. The stylesheet comes from the same package.
1818

1919
::script-types{exclude-components}
2020
::
2121

2222
## Setup
2323

24-
Install MapLibre for its TypeScript definitions, then enable the registry entry:
24+
Install MapLibre v6, then enable the registry entry:
2525

2626
```bash
2727
pnpm add -D maplibre-gl

‎packages/script/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"unimport",
7070
"#nuxt-scripts/types",
7171
"posthog-js",
72+
"maplibre-gl",
7273
"@nuxt/devtools-kit",
7374
"sirv"
7475
]
@@ -84,7 +85,7 @@
8485
"@types/vimeo__player": "^2.18.3",
8586
"@types/youtube": "^0.1.0",
8687
"@unhead/vue": "^3.3.1",
87-
"maplibre-gl": "^5.24.0",
88+
"maplibre-gl": "^6.0.0",
8889
"posthog-js": "^1.0.0",
8990
"unhead": "^3.3.1"
9091
},

‎packages/script/src/registry-types.json‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@
822822
{
823823
"name": "MapLibreOptions",
824824
"kind": "const",
825-
"code": "export const MapLibreOptions = object({\n /**\n * Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins\n * loading. Disable this when supplying the stylesheet through Nuxt.\n * @default true\n */\n injectStyles: optional(boolean()),\n /**\n * Stylesheet URL used when `injectStyles` is enabled.\n * @default 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'\n */\n stylesheetUrl: optional(string()),\n /**\n * Worker URL for the CSP-compatible MapLibre build. Pair this with a custom\n * `scriptInput.src` that loads `maplibre-gl-csp.js`.\n */\n workerUrl: optional(string()),\n})"
825+
"code": "export const MapLibreOptions = object({\n /**\n * Inject the MapLibre stylesheet when the script begins loading. Disable\n * this when supplying the stylesheet through Nuxt.\n * @default true\n */\n injectStyles: optional(boolean()),\n /**\n * Stylesheet URL used when `injectStyles` is enabled. Defaults to the\n * stylesheet shipped in the `maplibre-gl` package.\n */\n stylesheetUrl: optional(string()),\n /**\n * Worker URL passed to `maplibregl.setWorkerUrl()`. Set this when serving\n * the MapLibre worker from your own origin.\n */\n workerUrl: optional(string()),\n})"
826826
},
827827
{
828828
"name": "MapLibreApi",
@@ -837,7 +837,7 @@
837837
{
838838
"name": "ScriptMapLibreMapProps",
839839
"kind": "interface",
840-
"code": "interface ScriptMapLibreMapProps {\n /**\n * Defines when the MapLibre script loads.\n * @default 'visible'\n */\n trigger?: ElementScriptTrigger\n /** MapLibre style URL or inline style specification. */\n mapStyle: string | MapLibre.StyleSpecification\n /** Initial and reactively controlled map center. */\n center: MapLibre.LngLatLike\n /** Initial and reactively controlled zoom level. @default 12 */\n zoom?: number\n /** Initial and reactively controlled bearing in degrees. @default 0 */\n bearing?: number\n /** Initial and reactively controlled pitch in degrees. @default 0 */\n pitch?: number\n /** Options passed to `new maplibregl.Map()`. Dedicated props take precedence. */\n options?: Omit<MapLibre.MapOptions, 'container'>\n /** Inject MapLibre's stylesheet when the script begins loading. @default true */\n injectStyles?: boolean\n /** Custom MapLibre stylesheet URL. */\n stylesheetUrl?: string\n /** Worker URL used with MapLibre's CSP-compatible build. */\n workerUrl?: string\n /** Width reserved before the map loads. @default 640 */\n width?: number | string\n /** Height reserved before the map loads. @default 400 */\n height?: number | string\n /** Accessible name for an interactive map. @default 'Interactive map' */\n ariaLabel?: string\n /** Disable map input and remove it from the accessibility tree when decorative. @default true */\n interactive?: boolean\n /** Attributes applied to the outer layout container. */\n rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>\n}"
840+
"code": "interface ScriptMapLibreMapProps {\n /**\n * Defines when the MapLibre script loads.\n * @default 'visible'\n */\n trigger?: ElementScriptTrigger\n /** MapLibre style URL or inline style specification. */\n mapStyle: string | MapLibre.StyleSpecification\n /** Initial and reactively controlled map center. */\n center: MapLibre.LngLatLike\n /** Initial and reactively controlled zoom level. @default 12 */\n zoom?: number\n /** Initial and reactively controlled bearing in degrees. @default 0 */\n bearing?: number\n /** Initial and reactively controlled pitch in degrees. @default 0 */\n pitch?: number\n /** Options passed to `new maplibregl.Map()`. Dedicated props take precedence. */\n options?: Omit<MapLibre.MapOptions, 'container'>\n /** Inject MapLibre's stylesheet when the script begins loading. @default true */\n injectStyles?: boolean\n /** Custom MapLibre stylesheet URL. */\n stylesheetUrl?: string\n /** Worker URL passed to `maplibregl.setWorkerUrl()`. */\n workerUrl?: string\n /** Width reserved before the map loads. @default 640 */\n width?: number | string\n /** Height reserved before the map loads. @default 400 */\n height?: number | string\n /** Accessible name for an interactive map. @default 'Interactive map' */\n ariaLabel?: string\n /** Disable map input and remove it from the accessibility tree when decorative. @default true */\n interactive?: boolean\n /** Attributes applied to the outer layout container. */\n rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>\n}"
841841
},
842842
{
843843
"name": "ScriptMapLibreMapEvents",
@@ -1482,21 +1482,20 @@
14821482
"name": "injectStyles",
14831483
"type": "boolean",
14841484
"required": false,
1485-
"description": "Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins loading. Disable this when supplying the stylesheet through Nuxt.",
1485+
"description": "Inject the MapLibre stylesheet when the script begins loading. Disable this when supplying the stylesheet through Nuxt.",
14861486
"defaultValue": "true"
14871487
},
14881488
{
14891489
"name": "stylesheetUrl",
14901490
"type": "string",
14911491
"required": false,
1492-
"description": "Stylesheet URL used when `injectStyles` is enabled.",
1493-
"defaultValue": "'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'"
1492+
"description": "Stylesheet URL used when `injectStyles` is enabled. Defaults to the stylesheet shipped in the `maplibre-gl` package."
14941493
},
14951494
{
14961495
"name": "workerUrl",
14971496
"type": "string",
14981497
"required": false,
1499-
"description": "Worker URL for the CSP-compatible MapLibre build. Pair this with a custom `scriptInput.src` that loads `maplibre-gl-csp.js`."
1498+
"description": "Worker URL passed to `maplibregl.setWorkerUrl()`. Set this when serving the MapLibre worker from your own origin."
15001499
}
15011500
],
15021501
"AhrefsAnalyticsOptions": [

‎packages/script/src/registry.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export const registryMeta: RegistryScriptMeta[] = [
162162
// content
163163
m('googleMaps','Google Maps','content','useScriptGoogleMaps',{},null),
164164
m('leaflet','Leaflet','content','useScriptLeaflet',{bundle: true},null),
165-
m('maplibre','MapLibre GL JS','content','useScriptMapLibre',{bundle: true},null),
165+
m('maplibre','MapLibre GL JS','content','useScriptMapLibre',{},null),
166166
m('instagramEmbed','Instagram Embed','content',false,{},null),
167167
m('xEmbed','X Embed','content',false,{},null),
168168
m('blueskyEmbed','Bluesky Embed','content',false,{},null),
@@ -708,9 +708,10 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
708708
composableName: 'useScriptMapLibre',
709709
schema: MapLibreOptions,
710710
label: 'MapLibre GL JS',
711-
src: 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js',
711+
// MapLibre GL JS v6 is ESM-only, so it loads from the `maplibre-gl`
712+
// package rather than a CDN script tag.
713+
src: false,
712714
category: 'content',
713-
bundle: true,
714715
}),
715716
def('blueskyEmbed',{
716717
composableName: false,

‎packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface ScriptMapLibreMapProps {
2727
injectStyles?:boolean
2828
/** Custom MapLibre stylesheet URL. */
2929
stylesheetUrl?:string
30-
/** Worker URL used with MapLibre's CSP-compatible build. */
30+
/** Worker URL passed to `maplibregl.setWorkerUrl()`. */
3131
workerUrl?:string
3232
/** Width reserved before the map loads. @default640*/
3333
width?:number|string
Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
exportconstMAPLIBRE_STYLESHEET_URL='https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'
2-
exportconstMAPLIBRE_STYLESHEET_INTEGRITY='sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK'
3-
41
constMAPLIBRE_STYLE_ID='nuxt-scripts-maplibre-styles'
52

6-
exportfunctionconfigureMapLibreWorker(maplibre: {setWorkerUrl: (value: string)=>void}|undefined,workerUrl?: string): void{
7-
if(maplibre&&workerUrl)
3+
interfaceMapLibreWorkerApi{
4+
setWorkerUrl: (value: string)=>void
5+
}
6+
7+
/**
8+
* Points MapLibre at its worker.
9+
*
10+
* MapLibre v6 resolves the worker from `import.meta.url`, which does not
11+
* survive bundling, so the URL has to be set explicitly. `?worker&url` is
12+
* required over plain `?url`: the worker imports its sibling
13+
* `maplibre-gl-shared.mjs`.
14+
*
15+
* @see https://maplibre.org/maplibre-gl-js/docs/#installation
16+
*/
17+
exportasyncfunctionconfigureMapLibreWorker(maplibre: MapLibreWorkerApi|undefined,workerUrl?: string): Promise<void>{
18+
if(!maplibre)
19+
return
20+
if(workerUrl){
821
maplibre.setWorkerUrl(workerUrl)
22+
return
23+
}
24+
const{default: bundledWorkerUrl}=awaitimport('maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url')
25+
maplibre.setWorkerUrl(bundledWorkerUrl)
926
}
1027

11-
/** Injects MapLibre's required control and marker stylesheet once. */
12-
exportfunctionensureMapLibreStyles(stylesheetUrl=MAPLIBRE_STYLESHEET_URL): void{
28+
/** Injects a custom MapLibre control and marker stylesheet once. */
29+
exportfunctionensureMapLibreStyles(stylesheetUrl: string): void{
1330
if(typeofdocument==='undefined')
1431
return
1532

@@ -25,10 +42,5 @@ export function ensureMapLibreStyles(stylesheetUrl = MAPLIBRE_STYLESHEET_URL): v
2542
link.id=injectedStylesheets.length ? `${MAPLIBRE_STYLE_ID}-${injectedStylesheets.length+1}` : MAPLIBRE_STYLE_ID
2643
link.dataset.nuxtScriptsMaplibre='1'
2744

28-
if(stylesheetUrl===MAPLIBRE_STYLESHEET_URL){
29-
link.integrity=MAPLIBRE_STYLESHEET_INTEGRITY
30-
link.crossOrigin='anonymous'
31-
}
32-
3345
document.head.append(link)
3446
}
Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
importtype*asMapLibrefrom'maplibre-gl'
22
importtype{RegistryScriptInput}from'#nuxt-scripts/types'
3-
import{configureMapLibreWorker,ensureMapLibreStyles,MAPLIBRE_STYLESHEET_URL}from'../maplibre-styles'
3+
import{configureMapLibreWorker,ensureMapLibreStyles}from'../maplibre-styles'
44
import{useRegistryScript}from'../utils'
55
import{MapLibreOptions}from'./schemas'
66

@@ -13,34 +13,53 @@ export interface MapLibreApi {
1313
}
1414

1515
declare global {
16-
interfaceWindowextendsMapLibreApi{}
16+
interfaceWindow{
17+
maplibregl?: typeofMapLibre
18+
}
1719
}
1820

21+
/**
22+
* MapLibre GL JS v6 is an ESM-only distribution. It has no UMD build, and its
23+
* worker resolves against `import.meta.url`, so it is loaded from the
24+
* `maplibre-gl` package instead of a CDN script tag.
25+
*/
1926
exportfunctionuseScriptMapLibre<TextendsMapLibreApi>(_options?: MapLibreInput){
20-
returnuseRegistryScript<T,typeofMapLibreOptions>('maplibre',(options,context)=>{
27+
returnuseRegistryScript<T,typeofMapLibreOptions>('maplibre',(options)=>{
2128
constinjectStyles=options?.injectStyles!==false
22-
conststylesheetUrl=options?.stylesheetUrl||MAPLIBRE_STYLESHEET_URL
23-
constusesDefaultSource=!context.scriptInput?.src
29+
conststylesheetUrl=options?.stylesheetUrl
2430

2531
return{
26-
scriptInput: {
27-
src: 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js',
28-
...(usesDefaultSource
29-
? {
30-
integrity: 'sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp',
31-
crossorigin: 'anonymous',
32-
}
33-
: {}),
34-
},
35-
clientInit: injectStyles ? ()=>ensureMapLibreStyles(stylesheetUrl) : undefined,
32+
scriptMode: 'npm',
3633
schema: import.meta.dev ? MapLibreOptions : undefined,
3734
scriptOptions: {
3835
use(){
39-
constmaplibregl=window.maplibregl
40-
configureMapLibreWorker(maplibregl,options?.workerUrl)
41-
return{ maplibregl }
36+
returnimport.meta.client&&window.maplibregl
37+
? {maplibregl: window.maplibregl}
38+
: undefined
4239
},
4340
},
41+
clientInit: import.meta.server
42+
? undefined
43+
: async(ctx)=>{
44+
constthrowIfAborted=()=>{
45+
if(ctx?.signal.aborted)
46+
throwctx.signal.reason||newError('Loading MapLibre was aborted')
47+
}
48+
throwIfAborted()
49+
50+
if(injectStyles){
51+
if(stylesheetUrl)
52+
ensureMapLibreStyles(stylesheetUrl)
53+
else
54+
awaitimport('maplibre-gl/dist/maplibre-gl.css')
55+
}
56+
57+
constmaplibregl=window.maplibregl||awaitimport('maplibre-gl')
58+
throwIfAborted()
59+
awaitconfigureMapLibreWorker(maplibregl,options?.workerUrl)
60+
window.maplibregl=maplibregl
61+
return{ maplibregl }
62+
},
4463
}
4564
},_options)
4665
}

‎packages/script/src/runtime/registry/schemas.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ export const LeafletOptions = object({
3030

3131
exportconstMapLibreOptions=object({
3232
/**
33-
* Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins
34-
* loading. Disable this when supplying the stylesheet through Nuxt.
33+
* Inject the MapLibre stylesheet when the script begins loading. Disable
34+
* this when supplying the stylesheet through Nuxt.
3535
* @default true
3636
*/
3737
injectStyles: optional(boolean()),
3838
/**
39-
* Stylesheet URL used when `injectStyles` is enabled.
40-
* @default 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'
39+
* Stylesheet URL used when `injectStyles` is enabled. Defaults to the
40+
* stylesheet shipped in the `maplibre-gl` package.
4141
*/
4242
stylesheetUrl: optional(string()),
4343
/**
44-
* Worker URL for the CSP-compatible MapLibre build. Pair this with a custom
45-
* `scriptInput.src` that loads `maplibre-gl-csp.js`.
44+
* Worker URL passed to `maplibregl.setWorkerUrl()`. Set this when serving
45+
* the MapLibre worker from your own origin.
4646
*/
4747
workerUrl: optional(string()),
4848
})

‎packages/script/src/script-meta.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ export const scriptMeta = {
188188
trackedData: [],
189189
},
190190
maplibre: {
191-
urls: ['https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js'],
191+
// Loaded from the `maplibre-gl` package, not a CDN.
192+
urls: [],
192193
trackedData: [],
193194
},
194195

0 commit comments

Comments
 (0)