As discussed in Leaflet github repo ( e.g. here and here ) integrate the library with our frontend framework and build tools could be tricky.
In this short post I will describe how to setup Leaflet with Svelte and Vite.
Quickstart (base setup)
For sure we need to install the module via npm or yarn or whatever you are using as package manager.
npm install --save-dev leaflet
In our svelte module we start importing Leaflet then load the map during svelte module mount callback:
<script>
import { onMount } from "svelte";
import * as L from "leaflet";
let map = null;
onMount(() => {
map = L.map("map").setView([45.0659, 7.67], 11);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
});
</script>
<div id="map" />
The above code is equivalent to the Leaflet quickstart tutorial.
Add the marker
The problem raises when we add a marker with default icon:
<script>
import { onMount } from "svelte";
import * as L from "leaflet";
let map = null;
onMount(() => {
map = L.map("map").setView([45.0659, 7.67], 11);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
L.marker([45.0659, 7.67]).addTo(map)
});
</script>
<div id="map" />
The code should work in development setup, but it might not work for production, indeed Leaflet will looking for marker icon at the root /marker-icon.png
(the same for other images like the shadow), but our Vite build output doesn’t know about these images and doesn’t generate or expose them.
Fix marker for production build
To solve the issue we need to let Vite know we are using these images and set the right image URLs and paths for Leaflet:
<script>
import { onMount } from "svelte";
import * as L from "leaflet";
import markerIconUrl from "../node_modules/leaflet/dist/images/marker-icon.png";
import markerIconRetinaUrl from "../node_modules/leaflet/dist/images/marker-icon-2x.png";
import markerShadowUrl from "../node_modules/leaflet/dist/images/marker-shadow.png";
let map = null;
onMount(() => {
map = L.map("map").setView([45.0659, 7.67], 11);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
L.Icon.Default.prototype.options.iconUrl = markerIconUrl;
L.Icon.Default.prototype.options.iconRetinaUrl = markerIconRetinaUrl;
L.Icon.Default.prototype.options.shadowUrl = markerShadowUrl;
L.Icon.Default.imagePath = ""; // necessary to avoid Leaflet adds some prefix to image path.
L.marker([45.0659, 7.67]).addTo(map)
});
</script>
<div id="map" />
Please note the relative paths of imports: it supposed that the above svelte module resides in a subfolder like src
, so node_modules
folder is in the parent directory.
Production build should work now, but you may have noticed that Vite didn’t generate the image files, indeed there is a default build config that makes small assets as inlined base64.
Avoid assets as inlined base64
If we want Vite creates images as standalone files just change the build.assetsInlineLimit option and set it to 0, for example.
// ...
export default defineConfig({
plugins: [svelte()],
build: {
// ...
assetsInlineLimit: 0,
// ...
},
// ...
})
The production build will generate images in the assets folder, ready to be served from our webserver.
npm run build
# output:
# ...
# ...
vite v4.3.9 building for production...
✓ 47 modules transformed.
dist/index.html 0.44 kB │ gzip: 0.30 kB
dist/assets/marker-shadow-264f5c64.png 0.62 kB
dist/assets/layers-1dbbe9d0.png 0.70 kB
dist/assets/layers-2x-066daca8.png 1.26 kB
dist/assets/marker-icon-574c3a5c.png 1.47 kB
dist/assets/marker-icon-2x-00179c4c.png 2.46 kB
# ...
Whatever build configuration we choose, Vite will generate the right URL for us: a base64 string or a path to the resource in the assets folder.