Skip to content

jburnhams/leaflet-node

Repository files navigation

Leaflet-node

npm version Node.js Version TypeScript

Leaflet-node brings the full Leaflet map API to Node.js so you can render maps without a browser.
Reuse the same map setup for server-side image generation, automated tests, and CI pipelines.

Installation

Requirements

  • Node.js >= 20
  • Linux users need glibc >= 2.18 for the bundled @napi-rs/canvas
  • Peer dependency: leaflet ^1.9.0

Package managers

npm install leaflet-node leaflet
yarn add leaflet-node leaflet
pnpm add leaflet-node leaflet
bun add leaflet-node leaflet

Quick example

import L from 'leaflet-node';

const container = document.createElement('div');
container.style.width = '600px';
container.style.height = '400px';

const map = L.map(container);
map.setView([51.505538, -0.090005], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors'
}).addTo(map);

map.setSize(600, 400);
await new Promise((resolve) => setTimeout(resolve, 1000));
await map.saveImage('map.png');

Testing with Jest

Leaflet-node can run inside Jest with either the default jsdom environment for API-only tests or the node environment for full canvas rendering.

API-only tests (no rendering)

Use the default environment and mock Leaflet to avoid creating a native canvas:

// tests/setup.ts
import { jest } from '@jest/globals';
const L = require('leaflet-node');

jest.doMock('leaflet', () => L);

Rendering tests (with canvas output)

Switch to the node environment so leaflet-node can provide its own jsdom instance and native canvas bindings:

/**
 * @jest-environment node
 */
import L from 'leaflet-node';

test('render map', async () => {
  const container = document.createElement('div');
  const map = L.map(container);

  map.setView([51.505538, -0.090005], 13);
  map.setSize(512, 512);

  await map.saveImage('map.png');
});

Known Issues

Tile Loading Warnings:

You may see warnings about "dynamic import callback" or "experimental-vm-modules" when running tests with tile layers in Jest. This is a Jest limitation with VM modules and doesn't affect the actual rendering—tiles that load successfully are rendered correctly.

To suppress these warnings, you can:

  1. Run Jest with experimental VM modules support:
NODE_OPTIONS='--experimental-vm-modules' jest
  1. Or configure Jest to use isolated modules:
// jest.config.js
export default {
  testEnvironment: 'node',
  globals: {
    'ts-jest': {
      isolatedModules: true
    }
  }
};

Tip

Need a ready-to-use map in tests? leaflet-node/testing exposes createTestMap, cleanupTestMaps, waitForTiles, and waitForMapReady helpers that work in both Vitest and Jest.

import { createTestMap, waitForMapReady } from 'leaflet-node/testing';

const map = createTestMap({ width: 400, height: 300 });
await waitForMapReady(map);

Exporting images

Maps can be exported to disk or kept in-memory:

// Save to disk with optional encoder options
await map.saveImage('map.jpeg', { format: 'jpeg', quality: 0.85 });

// Get a Buffer for further processing
const pngBuffer = await map.toBuffer('png');

If you need full control of the canvas, use map.toBuffer() and write the result yourself:

import { promises as fs } from 'fs';

const buffer = await map.toBuffer('png');
await fs.writeFile('map.png', buffer);

Tile loading helpers

Wait for tiles (with optional progress callbacks) before exporting:

import { waitForTiles } from 'leaflet-node/testing';

const layer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
layer.addTo(map);

await waitForTiles(layer, {
  timeout: 60_000,
  onProgress: ({ loaded, total }) => {
    console.log(`Loaded ${loaded}/${total} tiles`);
  },
});

Or wait for every tile layer on a map at once:

import { waitForMapReady } from 'leaflet-node/testing';

await waitForMapReady(map, {
  onTileProgress: (layer, progress) => {
    console.log(layer.getAttribution(), progress);
  },
});

📚 View the full documentation and live examples at jburnhams.github.io/leaflet-node.

Configuring fallback fonts

Leaflet-node automatically attempts to register the bundled Noto Sans fallback fonts. In environments where document.currentScript is unavailable (for example, Node.js test runners without a DOM shim), you can provide an explicit path to the font assets to avoid warning messages:

process.env.LEAFLET_NODE_FONT_BASE_PATH = '/absolute/path/to/NotoSans-Regular.ttf';
const L = await import('leaflet-node');

Alternatively, you can set the base path programmatically after importing the package:

import L, { setFontAssetBasePath } from 'leaflet-node';

setFontAssetBasePath('/absolute/path/to/NotoSans-Regular.ttf');

The base path can point directly to a font file or to a directory containing the bundled NotoSans-Regular.ttf asset.

Proxy configuration

Leaflet-node honours standard proxy environment variables:

export HTTPS_PROXY=http://proxy.example.com:8080
export HTTP_PROXY=http://proxy.example.com:8080

Set them before running exports to route tile downloads through your proxy.

Performance tips

  • Reuse map instances – keep a single map around and update the view instead of creating a new L.map() for every render.
  • Batch exports – kick off multiple independent exports with Promise.all() when your server has headroom.
  • Manage memory – always call map.remove() and drop references once you are done with a map so Node.js can reclaim resources.

Questions? Open an issue on GitHub.

About

Leaflet for node.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 8