Интеграция с Vite

Работа с MapLibre GL JS в связке с Vite начинается с установки основных пакетов. В экосистеме Vite предпочтение отдаётся ESM-модулям, поэтому MapLibre GL JS подключается напрямую как модуль без дополнительных сборщиков.

npm install maplibre-gl
npm install vite

Дополнительно требуется CSS-файл библиотеки, поскольку визуальная часть карты зависит от встроенных стилей.

Подключение MapLibre GL JS в модуле

Базовая инициализация карты строится вокруг импорта библиотеки и контейнера DOM:

import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [37.6173, 55.7558],
  zoom: 10
});

Контейнер для карты создаётся в HTML:

<div id="map" style="width: 100%; height: 100vh;"></div>

Особенности работы MapLibre GL JS в Vite

Vite использует нативные ES-модули и строгую систему обработки зависимостей. MapLibre GL JS, в свою очередь, использует Web Worker для рендеринга тайлов и слоя карты. В классических сборщиках этот процесс скрыт, но в Vite требуется явная настройка worker-скрипта.

Основная проблема связана с загрузкой worker файла. При отсутствии конфигурации карта может не отображаться или выдавать ошибки WebGL pipeline.

Настройка Web Worker в Vite

MapLibre GL JS требует корректного указания пути к worker-файлу:

import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

maplibregl.workerUrl = new URL(
  'maplibre-gl/dist/maplibre-gl-csp-worker',
  import.meta.url
).toString();

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [37.6173, 55.7558],
  zoom: 10
});

Использование new URL(..., import.meta.url) позволяет Vite корректно обработать worker как отдельный ассет и включить его в сборку.

Обработка стилей и статических ресурсов

CSS MapLibre GL JS должен импортироваться как модульный ресурс:

import 'maplibre-gl/dist/maplibre-gl.css';

Vite автоматически инлайнит или выносит стили в отдельный бандл в зависимости от режима сборки.

При использовании кастомных иконок или изображений для слоёв (например, addImage) необходимо учитывать, что пути к ресурсам должны быть либо импортированы, либо размещены в public директории:

map.loadImage('/icons/marker.png', (error, image) => {
  if (error) return;
  map.addImage('marker', image);
});

Конфигурация Vite для MapLibre GL JS

Базовая конфигурация Vite может оставаться минимальной, однако в некоторых случаях требуется корректировка optimizeDeps для предотвращения проблем с pre-bundling:

import { defineConfig } from 'vite';

export default defineConfig({
  optimizeDeps: {
    include: ['maplibre-gl']
  }
});

При использовании старых версий зависимостей может возникать необходимость исключения MapLibre из оптимизации:

optimizeDeps: {
  exclude: ['maplibre-gl']
}

Работа с base path в production

При развертывании на поддиректории важно учитывать параметр base в Vite-конфигурации. MapLibre GL JS загружает ресурсы относительно текущего окружения, поэтому неправильный base может привести к ошибкам загрузки worker и стилей.

export default defineConfig({
  base: '/app/'
});

В этом случае worker URL также корректно резолвится благодаря import.meta.url, что предотвращает необходимость ручной настройки путей.

Поддержка TypeScript

MapLibre GL JS предоставляет встроенные типы, поэтому интеграция с TypeScript минимально затратна.

import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const map: maplibregl.Map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [0, 0],
  zoom: 2
});

Типизация событий карты позволяет безопасно работать с интерактивными слоями:

map.on('click', (e) => {
  const coordinates = e.lngLat;
});

Разделение кода и ленивую загрузку карты

Vite поддерживает динамический импорт, что позволяет загружать MapLibre GL JS только при необходимости. Это снижает начальный размер бандла.

async function initMap() {
  const maplibregl = await import('maplibre-gl');
  await import('maplibre-gl/dist/maplibre-gl.css');

  maplibregl.workerUrl = new URL(
    'maplibre-gl/dist/maplibre-gl-csp-worker',
    import.meta.url
  ).toString();

  new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [0, 0],
    zoom: 3
  });
}

Такой подход часто используется в приложениях, где карта является второстепенным элементом интерфейса.

Работа с переменными окружения

Vite предоставляет доступ к .env переменным через import.meta.env. В контексте MapLibre GL JS это используется для управления стилями и API endpoints.

const map = new maplibregl.Map({
  container: 'map',
  style: import.meta.env.VITE_MAP_STYLE_URL,
  center: [30, 50],
  zoom: 4
});

Пример .env:

VITE_MAP_STYLE_URL=https://example.com/style.json

Интеграция с пользовательскими стилями Mapbox Style Spec

MapLibre GL JS полностью совместим со спецификацией Mapbox Style. В Vite-проекте стили могут храниться локально:

import style from './style.json';

const map = new maplibregl.Map({
  container: 'map',
  style
});

Vite позволяет импортировать JSON напрямую как модуль, что упрощает работу с конфигурацией карты.

Обработка ошибок загрузки ресурсов

При работе с картой важно учитывать ошибки загрузки тайлов и стилей:

map.on('error', (e) => {
  console.error('Map error:', e.error);
});

В Vite окружении такие ошибки часто связаны с неправильными путями к worker или отсутствием CORS-заголовков на сервере тайлов.

Кэширование и производительность

Vite в production режиме оптимизирует статические ресурсы, однако MapLibre GL JS имеет собственные механизмы кэширования тайлов и шейдеров WebGL.

Для повышения производительности используются следующие подходы:

  • ограничение числа одновременно загружаемых тайлов
  • использование локальных стилей вместо удалённых JSON
  • предварительная загрузка источников данных
  • отключение ненужных слоёв в ранних зумах

SSR и проблемы серверного рендеринга

MapLibre GL JS зависит от WebGL и window, поэтому не совместим с SSR напрямую. В Vite-проектах с SSR требуется условная инициализация:

if (typeof window !== 'undefined') {
  import('maplibre-gl').then((maplibregl) => {
    // инициализация карты
  });
}

В противном случае серверная сборка будет падать из-за отсутствия браузерного окружения.

Использование плагинов Vite в экосистеме карты

При расширении функциональности применяются дополнительные плагины Vite для работы с ресурсами:

  • обработка SVG-иконок как компонентов
  • оптимизация изображений для маркеров
  • инлайн небольших ассетов для снижения числа запросов

Пример подключения SVG как модуля:

import marker from './marker.svg?url';

map.loadImage(marker, (error, image) => {
  if (!error) map.addImage('marker', image);
});

Управление слоями и источниками данных

После инициализации карты добавление данных осуществляется через стандартные API MapLibre:

map.on('load', () => {
  map.addSource('points', {
    type: 'geojson',
    data: '/data/points.geojson'
  });

  map.addLayer({
    id: 'points-layer',
    type: 'circle',
    source: 'points',
    paint: {
      'circle-radius': 6,
      'circle-color': '#ff0000'
    }
  });
});

Vite автоматически обслуживает статические GeoJSON файлы из public директории.

Организация архитектуры в Vite-проекте

Типичная структура проекта с MapLibre GL JS включает разделение логики карты и UI:

src/
  map/
    map.js
    layers.js
    sources.js
  components/
    MapContainer.vue
  styles/
    map.css
public/
  data/
  icons/

Такое разделение упрощает масштабирование карты и внедрение сложных визуализаций.

Горячая перезагрузка (HMR)

Vite поддерживает HMR, однако MapLibre GL JS требует осторожности. Повторная инициализация карты без уничтожения предыдущего экземпляра приводит к утечкам памяти WebGL.

Корректный подход включает очистку:

let map;

export function initMap() {
  if (map) {
    map.remove();
  }

  map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json'
  });
}

HMR-сценарии требуют полного пересоздания canvas при изменениях модуля карты.