Move to MapLibre GL 6, loaded as an ES module

- 5.x has a critical XSS advisory (GHSA-jrc7-96c5-q579), fixed only in 6.4.1+
This commit is contained in:
codecalm committed 2026-10-04 17:43:33 +02:00
1 parent c60ea8e5da
commit b135a40064
8 files changed
+73 -52

No files matched your search

+3 -2
View File
@@ -57,8 +57,9 @@
},
"maplibre-gl": {
"npm": "maplibre-gl",
"js": ["dist/maplibre-gl.js"],
"css": ["dist/maplibre-gl.css"]
"module": { "file": "dist/maplibre-gl.mjs", "global": "maplibregl" },
"css": ["dist/maplibre-gl.css"],
"extra": ["dist/maplibre-gl.mjs", "dist/maplibre-gl-shared.mjs", "dist/maplibre-gl-worker.mjs"]
},
"jsvectormap": {
"npm": "jsvectormap",
+1 -1
View File
@@ -276,7 +276,7 @@
"jsvectormap": "^1.7.0",
"list.js": "^2.3.1",
"litepicker": "^2.0.12",
"maplibre-gl": "^5.24.0",
"maplibre-gl": "^6.11.2",
"nouislider": "^15.8.1",
"playwright": "1.62.1",
"plyr": "^3.8.4",
+26 -8
View File
@@ -48,20 +48,38 @@ The demos load their map tiles from [OpenFreeMap](https://openfreemap.org/). It
## Installation
Install MapLibre GL JS with npm. Tabler uses version 5, the last one that ships a classic script file:
```bash
npm install maplibre-gl@5
```
Or include it from a CDN. You need two files: the stylesheet and the library.
MapLibre GL JS is published as an ES module. Tabler uses version 6. Load the stylesheet, then import the library in a module script:
<Code
lang="html"
code={`<link rel="stylesheet" href="${site.cdnUrl}/dist/libs/maplibre-gl/dist/maplibre-gl.css" />
<script src="${site.cdnUrl}/dist/libs/maplibre-gl/dist/maplibre-gl.js"></script>`}
<script type="module">
import * as maplibregl from '${site.cdnUrl}/dist/libs/maplibre-gl/dist/maplibre-gl.mjs'
// Makes the library available to the other scripts on the page
window.maplibregl = maplibregl
</script>`}
/>
A module script runs before the `DOMContentLoaded` event, so the code examples on this page can use the `maplibregl` global. The library loads two more files from the same folder, `maplibre-gl-shared.mjs` and `maplibre-gl-worker.mjs`, so keep the three together when you host them yourself.
With a bundler, install the package and import it in your code:
```bash
npm install maplibre-gl
```
```js
import * as maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
```
<Danger title="Use version 6.4.1 or newer">
Older versions, including all of version 5, have a known security issue in the code that cleans the attribution text.
</Danger>
Browsers do not load ES modules from a page opened straight from the disk. Use a local server to see the maps while you work.
Tabler styles the map controls, popups and attribution to match the interface, in light and dark mode. These styles are in `tabler-vendors.css`, so load it after the MapLibre stylesheet:
<Code
+5 -1
View File
@@ -142,7 +142,7 @@ const editFilePath =
// docs-libs for this page. In dev, file names stay unchanged
// (e.g. dist/plyr.min.js keeps .min).
type Lib = { npm?: string; js?: string[]; css?: string[] };
type Lib = { npm?: string; js?: string[]; css?: string[]; module?: { file: string; global: string } };
const libEntries = Object.entries(libs as Record<string, Lib>);
const libUrl = (lib: Lib, file: string) =>
isExternal(file)
@@ -151,6 +151,9 @@ const libUrl = (lib: Lib, file: string) =>
const docsLibCss = libEntries
.filter(([name]) => docsLibs.includes(name))
.flatMap(([, lib]) => (lib.css ?? []).map((file) => libUrl(lib, file)));
const docsLibModules = libEntries
.filter(([name]) => docsLibs.includes(name))
.flatMap(([, lib]) => (lib.module ? [`import * as lib from '${libUrl(lib, lib.module.file)}'; window.${lib.module.global} = lib`] : []));
const docsLibJs = libEntries
.filter(([name]) => docsLibs.includes(name))
.flatMap(([, lib]) => (lib.js ?? []).map((file) => libUrl(lib, file)));
@@ -517,6 +520,7 @@ const relatedPages = await Promise.all(
</Fragment>
)
}
{docsLibModules.map((code) => <Fragment set:html={`<script type="module">${code}</script>`} />)}
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
+24 -37
View File
@@ -199,8 +199,8 @@ importers:
specifier: ^2.0.12
version: 2.0.12
maplibre-gl:
specifier: ^5.24.0
version: 5.24.0
specifier: ^6.11.2
version: 6.11.2
nouislider:
specifier: ^15.8.1
version: 15.8.1
@@ -1289,24 +1289,17 @@ packages:
'@mapbox/tiny-sdf@2.2.0':
resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==}
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/unitbezier@1.0.0':
resolution: {integrity: sha512-fqd515fjBmANKGGsQ286E2Wvj/XvDFpGzwJxq4CI6jMQue6Oy04uCKp+JWKF00xRTmk6cEu1jPJ9p3xqH8YWqQ==}
'@mapbox/vector-tile@2.0.5':
resolution: {integrity: sha512-pXj8m7KTsqZt+1jsE0xIpGvqTSbblfkuEJL/NJmNePMtEwxO8V3XMDo9WMSfDeqHvCtBI9Lmt4mGcGR10zecmw==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@mapbox/vector-tile@3.0.0':
resolution: {integrity: sha512-Qf10S1uIHMk20ri/IVBnpS+esUEkVaR5Hftmz88jTInrpmWgPGJfPe3LVjjlE77trLx8tH6qjTG7uWH9hIq/0Q==}
'@maplibre/geojson-vt@6.1.1':
resolution: {integrity: sha512-FVMOcmSP/yqol45t7StApEyTL5/vmqBCuFhH9n+fFuINenhaX+YgHHIt1yJ86S8kln3uJLcMvmEU2cfn6E2eCQ==}
'@maplibre/maplibre-gl-style-spec@24.10.0':
resolution: {integrity: sha512-lichxSiagMEBBrqHF0trtMQH9RKh+9jUlIJl0qW0QHvt2H/tbvUWdE+ZzI2Jd0/pT7j/iavLonlPu7EQ/ixTOw==}
'@maplibre/maplibre-gl-style-spec@26.4.4':
resolution: {integrity: sha512-hG4RZR4PBd/+6A3ODdZ3AnQRF106ZQk1y3bhwDg8bH/A4Oz2tnRUJVVWs9BZa6gXZ1d3UKe89C+Mm+olGBELvA==}
hasBin: true
'@maplibre/mlt@1.3.0':
@@ -2210,6 +2203,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
bidi-js@1.1.0:
resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -3498,8 +3494,8 @@ packages:
make-synchronized@0.8.0:
resolution: {integrity: sha512-DZu4lwc0ffoFz581BSQa/BJl+1ZqIkoRQ+VejMlH0VrP4E86StAODnZujZ4sepumQj8rcP7wUnUBGM8Gu+zKUA==}
maplibre-gl@5.24.0:
resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==}
maplibre-gl@6.11.2:
resolution: {integrity: sha512-Xh06pxoipjX/Ad1sUPGhiNh9go9naCNGZZ1IJm3sIeqK1kYGuMaMDSTIGDQ2igOLfVyukJU7kvEOA089VO7Z7g==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
markdown-extensions@2.0.0:
@@ -3964,10 +3960,6 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pbf@4.0.2:
resolution: {integrity: sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg==}
hasBin: true
pbf@5.1.2:
resolution: {integrity: sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w==}
hasBin: true
@@ -6153,23 +6145,19 @@ snapshots:
'@mapbox/tiny-sdf@2.2.0': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/unitbezier@1.0.0': {}
'@mapbox/vector-tile@2.0.5':
'@mapbox/vector-tile@3.0.0':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.2
'@mapbox/whoots-js@3.1.0': {}
pbf: 5.1.2
'@maplibre/geojson-vt@6.1.1':
dependencies:
kdbush: 4.1.0
'@maplibre/maplibre-gl-style-spec@24.10.0':
'@maplibre/maplibre-gl-style-spec@26.4.4':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.3
'@mapbox/unitbezier': 1.0.0
@@ -7133,6 +7121,10 @@ snapshots:
baseline-browser-mapping@2.11.6: {}
bidi-js@1.1.0:
dependencies:
require-from-string: 2.0.2
binary-extensions@2.3.0: {}
bindings@1.5.0:
@@ -8455,24 +8447,23 @@ snapshots:
make-synchronized@0.8.0: {}
maplibre-gl@5.24.0:
maplibre-gl@6.11.2:
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.3
'@mapbox/point-geometry': 1.1.0
'@mapbox/tiny-sdf': 2.2.0
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 2.0.5
'@mapbox/whoots-js': 3.1.0
'@mapbox/unitbezier': 1.0.0
'@mapbox/vector-tile': 3.0.0
'@maplibre/geojson-vt': 6.1.1
'@maplibre/maplibre-gl-style-spec': 24.10.0
'@maplibre/maplibre-gl-style-spec': 26.4.4
'@maplibre/mlt': 1.3.0
'@maplibre/vt-pbf': 4.3.2
'@types/geojson': 7946.0.16
bidi-js: 1.1.0
earcut: 3.2.4
gl-matrix: 3.4.4
kdbush: 4.1.0
murmurhash-js: 1.0.0
pbf: 4.0.2
pbf: 5.1.2
potpack: 2.1.0
quickselect: 3.0.0
tinyqueue: 3.0.0
@@ -9227,10 +9218,6 @@ snapshots:
pathe@2.0.3: {}
pbf@4.0.2:
dependencies:
resolve-protobuf-schema: 2.1.0
pbf@5.1.2:
dependencies:
resolve-protobuf-schema: 2.1.0
@@ -73,11 +73,13 @@ const themeAttributes = themeDataAttributes({ ...theme, 'navbar-position': navba
// whatever core's own watcher last emitted, which is unminified.
const min = import.meta.env.DEV ? '' : '.min'
type Lib = { npm?: string; js?: string[]; css?: string[] }
type Lib = { npm?: string; js?: string[]; css?: string[]; module?: { file: string; global: string } }
const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name]) => pageLibs.includes(name))
const libHref = (lib: Lib, file: string) => `/dist/libs/${lib.npm}/${file}`
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
// ES-module-only libraries: imported in a module script and exposed as a global (see BaseLayout)
const libModules = pageLibEntries.flatMap(([, lib]) => (lib.module ? [`import * as lib from '${libHref(lib, lib.module.file)}'; window.${lib.module.global} = lib`] : []))
---
<!doctype html>
@@ -177,6 +179,7 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
</nav>
{libJsFiles.map((src) => <script is:inline src={src} defer />)}
{libModules.map((code) => <Fragment set:html={`<script type="module">${code}</script>`} />)}
<script is:inline src={`/dist/js/tabler${min}.js`} defer></script>
<PageScripts />
+4 -1
View File
@@ -45,11 +45,13 @@ const rtlSuffix = rtl ? '.rtl' : ''
// build instead of hardcoding one variant.
const min = import.meta.env.DEV ? '' : '.min'
type Lib = { npm?: string; js?: string[]; css?: string[] }
type Lib = { npm?: string; js?: string[]; css?: string[]; module?: { file: string; global: string } }
const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name]) => pageLibs.includes(name))
const libHref = (lib: Lib, file: string) => `/dist/libs/${lib.npm}/${file}`
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
// ES-module-only libraries: imported in a module script and exposed as a global (see BaseLayout)
const libModules = pageLibEntries.flatMap(([, lib]) => (lib.module ? [`import * as lib from '${libHref(lib, lib.module.file)}'; window.${lib.module.global} = lib`] : []))
---
<!doctype html>
@@ -168,6 +170,7 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
</div>
{libJsFiles.map((src) => <script is:inline src={src} defer />)}
{libModules.map((code) => <Fragment set:html={`<script type="module">${code}</script>`} />)}
<script is:inline src={`/dist/js/tabler${min}.js`} defer></script>
<!-- components that render via CaptureScript (e.g. Chart) queue their init script
+6 -1
View File
@@ -39,7 +39,7 @@ const isProduction = process.env.VERCEL_ENV === 'production'
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
const rtlSuffix = rtl ? '.rtl' : ''
type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean }
type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean; module?: { file: string; global: string } }
const unknownLibs = pageLibs.filter((name) => !(name in libs))
if (unknownLibs.length > 0) {
throw new Error(`BaseLayout: unknown pageLibs ${unknownLibs.map((name) => `"${name}"`).join(', ')} — use a key from core/libs.json`)
@@ -48,6 +48,10 @@ const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name
// External URLs (http/https) are emitted verbatim; local files as ./dist/libs/{npm}/{file}.
const libHref = (lib: Lib, file: string) => (/^https?:\/\//.test(file) ? file : `${base}/dist/libs/${lib.npm}/${file}`)
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
// Libraries published as ES modules only: imported in a module script and
// handed to the classic page scripts as a global. Module scripts are deferred,
// so the global is set before DOMContentLoaded.
const libModules = pageLibEntries.flatMap(([, lib]) => (lib.module ? [`import * as lib from '${libHref(lib, lib.module.file)}'\n window.${lib.module.global} = lib`] : []))
const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean(lib.head) === head).flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
---
@@ -129,6 +133,7 @@ const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean
</Fragment>
)
}
{libModules.map((code) => <Fragment set:html={`<script type="module">\n ${code}\n </script>`} />)}
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
<script is:inline src={`${base}/dist/js/tabler${min}.js`} defer></script>