Skip to content

Vitepress e leaflet.markercluster

VitePress esegue il pre-rendering dell'app in Node.js durante la build di produzione, utilizzando le funzionalità di rendering lato server (SSR) di Vue: questo fatto impedisce di usare leaflet.markercluster all'interno di un progetto vitepress. Supponiamo di creare questa semplice mappa leaflet all'interno di un componente vue a sua volta utilizzato in un progetto vitepress:

vue
<template>
  <div id="map">
    <l-map
      :zoom="9"
      :center="[45.1, 9.1]"
      :use-global-leaflet="true"
    >
      <l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" :attribution="attribution"/>
      <l-marker-cluster-group>
        <l-marker :lat-lng="[45.1, 9.1]"/>
        <l-marker :lat-lng="[45.2, 9.2]"/>
        <l-marker :lat-lng="[45.3, 9.3]"/>
      </l-marker-cluster-group>
    </l-map>
  </div>
</template>

<script setup lang="ts">
import {LMarker, LMap, LTileLayer} from "@vue-leaflet/vue-leaflet";
import L, {marker} from 'leaflet'

globalThis.L = L
import {LMarkerClusterGroup} from 'vue-leaflet-markercluster'
import {ref} from 'vue'

import 'leaflet/dist/leaflet.css'
import 'vue-leaflet-markercluster/dist/style.css'

const attribution = ref("openstreetmap contributors")
const prefix = ref("leaflet")
</script>

<style scoped>
#map {
  height: 50vh;
  width: 100%;
}
</style>

Mentre yarn docs:dev o pnpm docs:dev (alias di vitepress dev docs, si veda la documentazione vitepress) eseguono con successo il codice qui sopra, la fase di build fallisce a causa appunto dell'utilizzo di SSR da parte di leaflet.markercluster. Fortunatamente è possibile evitare il problema utilizzando la sintassi import(), comunemente definitita "import dinamico". Esistono diverse implementazioni possibili (ad es. async components) ma in questo caso preferisco utilizzare la classica sintassi javascript all'interno dell'hook onBeforeMount(). Si può quindi eliminare il malfunzionamento in questo modo:

vue
<template>
  <div id="map-photos"/>
</template>

<script setup lang="ts">
import "leaflet/dist/leaflet.css";
import "leaflet.markercluster/dist/MarkerCluster.css"
import "leaflet.markercluster/dist/MarkerCluster.Default.css"
import {onBeforeMount} from "vue";

onBeforeMount(async () => {
  const L = await import("leaflet")
  const {MarkerClusterGroup} = await import('leaflet.markercluster/dist/leaflet.markercluster')

  const map = L.map("map-photos") // div id
  map.setView([45.1, 9.1], 8)
  L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png").addTo(map);
  let mcluster = new MarkerClusterGroup()
  let marker1 = new L.Marker([45.1, 9.1])
  let marker2 = new L.Marker([45.2, 9.2])
  let marker3 = new L.Marker([45.3, 9.3])
  marker1.addTo(mcluster)
  marker2.addTo(mcluster)
  marker3.addTo(mcluster)
  mcluster.addTo(map)
})
</script>

<style scoped>
#map-photos {
  width: 100%;
  height: 60vh;
}
</style>

Si noti che onBeforeMount() deve esser asincrono per funzionare. Ultimo ma non per importanza il package.json di questo progetto vitepress minimale deve contenere queste dipendenze:

json
{
  "name": "vitepress-ssr-build",
  "version": "1.0.0",
  "scripts": {
    "docs:dev": "vitepress dev docs",
    "docs:build": "pnpm i --frozen-lockfile && vitepress build docs",
    "docs:serve": "vitepress serve docs",
    "docs:preview": "vitepress preview docs --port 3000"
  },
  "license": "MIT",
  "type": "module",
  "dependencies": {
    "@vue-leaflet/vue-leaflet": "^0.10.1",
    "leaflet": "^1.9.4",
    "leaflet.markercluster": "^1.5.3",
    "vitepress": "^1.0.0-rc.10",
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "@tsconfig/recommended": "^1.0.2",
    "@types/geojson": "^7946.0.10",
    "@types/leaflet": "^1.9.4",
    "@types/leaflet.markercluster": "^1.5.2"
  }
}

Apprezzi il mio sito? Pagami un caffè
Referenze disponibili su richiesta. Autorizzo il trattamento dei miei dati personali in conformità al D. Lgs. n. 196/2003, art. 13, allo scopo di farmi proposte lavorative.