Service workers

Service Workers представляют собой промежуточный слой между веб-приложением и сетью, позволяющий перехватывать сетевые запросы, кэшировать ответы и управлять офлайн-доступом. В контексте картографических приложений на базе Mapbox GL JS это критически важный механизм, поскольку карта состоит из множества сетевых ресурсов: тайлов, стилей, шрифтов, спрайтов и GeoJSON-данных.

Mapbox GL JS активно использует динамическую загрузку ресурсов, и без дополнительного уровня контроля каждое перемещение карты может приводить к десяткам или сотням запросов. Service Worker позволяет превратить эту модель в кэш-ориентированную систему с предсказуемой производительностью.


Архитектура взаимодействия Mapbox GL JS и Service Worker

При стандартной работе Mapbox GL JS формирует запросы к следующим типам ресурсов:

  • векторные тайлы (.pbf)
  • растровые тайлы (.png, .jpg)
  • стили (style.json)
  • спрайты (sprite.json, sprite.png)
  • шрифты (glyphs, обычно через PBF)
  • источники данных GeoJSON

Service Worker перехватывает эти запросы на уровне fetch и принимает решение: вернуть кэшированную версию или обратиться к сети.

Ключевая особенность заключается в том, что Mapbox GL JS не требует специальных адаптаций для работы с Service Worker — вся интеграция происходит на уровне сети.


Регистрация Service Worker

Базовая регистрация выполняется в основном JavaScript-коде приложения:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', {
    scope: '/'
  });
}

После регистрации браузер начинает проксировать сетевые запросы через sw.js, если они соответствуют области действия (scope).


Перехват запросов карты

Основная логика строится вокруг события fetch:

self.addEventListener('fetch', (event) => {
  const requestUrl = new URL(event.request.url);

  if (requestUrl.pathname.includes('/tiles/')) {
    event.respondWith(handleTileRequest(event.request));
    return;
  }

  event.respondWith(fetch(event.request));
});

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


Кэширование векторных тайлов

Векторные тайлы являются наиболее частым источником сетевой нагрузки. Их кэширование даёт максимальный прирост производительности.

const TILE_CACHE = 'mapbox-tiles-v1';

async function handleTileRequest(request) {
  const cache = await caches.open(TILE_CACHE);
  const cached = await cache.match(request);

  if (cached) {
    return cached;
  }

  const response = await fetch(request);
  cache.put(request, response.clone());

  return response;
}

Особенность Mapbox GL JS заключается в том, что тайлы имеют предсказуемую структуру URL, что делает их удобными для кэширования по запросу.


Стратегии кэширования

Cache First

Подходит для статических данных карты (например, базовые стили или редко обновляемые слои).

  • сначала проверяется кэш
  • затем сеть

Преимущество заключается в высокой скорости отображения карты после первого посещения.


Network First

Используется для данных, которые часто обновляются, например:

  • динамические GeoJSON-слои
  • пользовательские маркеры
  • оперативные данные
async function networkFirst(request) {
  const cache = await caches.open('dynamic-data');

  try {
    const response = await fetch(request);
    cache.put(request, response.clone());
    return response;
  } catch (e) {
    return cache.match(request);
  }
}

Stale-While-Revalidate

Наиболее сбалансированная стратегия для картографических ресурсов:

  • мгновенная отдача из кэша
  • фоновое обновление
async function staleWhileRevalidate(request) {
  const cache = await caches.open('map-cache');
  const cached = await cache.match(request);

  const fetchPromise = fetch(request).then((response) => {
    cache.put(request, response.clone());
    return response;
  });

  return cached || fetchPromise;
}

Кэширование стилей Mapbox

Файлы стилей Mapbox GL JS представляют собой JSON-документы, описывающие слои, источники и визуальные параметры карты.

Особенность заключается в том, что стиль может ссылаться на множество внешних ресурсов, поэтому кэширование только JSON недостаточно.

const STYLE_CACHE = 'map-style';

self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('style.json')) {
    event.respondWith(cacheFirst(event.request, STYLE_CACHE));
  }
});

Важно учитывать версионирование стилей, так как изменение конфигурации слоёв может привести к несовместимости кэшированных тайлов.


Кэширование шрифтов и спрайтов

Mapbox GL JS использует glyph-тайлы для рендеринга текста и спрайты для иконок.

Шрифты обычно запрашиваются по шаблону:

/fonts/{fontstack}/{range}.pbf

Спрайты:

/sprite.png
/sprite.json

Их кэширование особенно эффективно, так как они редко изменяются.

const STATIC_CACHE = 'map-static';

async function cacheFirst(request, cacheName) {
  const cache = await caches.open(cacheName);
  const cached = await cache.match(request);

  if (cached) return cached;

  const response = await fetch(request);
  cache.put(request, response.clone());

  return response;
}

Работа с офлайн-картами

Полноценная офлайн-карта требует предварительной загрузки регионов. Mapbox GL JS сам по себе не предоставляет полного офлайн-движка, поэтому Service Worker становится ключевым компонентом.

Стратегия включает:

  • предварительное скачивание тайлов
  • кэширование стилей
  • сохранение шрифтов и спрайтов
async function precacheTiles(tileUrls) {
  const cache = await caches.open('offline-map');

  for (const url of tileUrls) {
    const response = await fetch(url);
    await cache.put(url, response);
  }
}

Ограничения кэширования

Несмотря на гибкость Service Worker, существуют ограничения:

  • ограниченный объём Cache Storage
  • невозможность кэшировать потоковые обновления WebSocket напрямую
  • необходимость учитывать CORS-заголовки Mapbox API
  • риск устаревших тайлов при изменении стиля

Особенно важно учитывать, что Mapbox GL JS может генерировать большое количество уникальных URL для тайлов, что увеличивает фрагментацию кэша.


Инвалидация кэша

При обновлении стилей или данных требуется контролируемая очистка кэша.

async function clearOldCaches() {
  const keys = await caches.keys();

  for (const key of keys) {
    if (key.startsWith('map-') && key !== 'mapbox-tiles-v2') {
      await caches.delete(key);
    }
  }
}

Версионирование кэша является основной стратегией управления обновлениями.


Безопасность и изоляция данных

Service Worker работает в отдельном контексте и не имеет прямого доступа к DOM. В картографических приложениях это снижает риски, связанные с:

  • подменой тайлов
  • внедрением несанкционированных источников данных
  • перехватом пользовательских слоёв

Однако необходимо учитывать, что любой закэшированный ресурс сохраняется в браузере и должен рассматриваться как потенциально долговременное состояние данных.


Производительность в связке Mapbox GL JS и Service Worker

Оптимизация достигается за счёт:

  • уменьшения сетевых запросов
  • локальной доставки тайлов
  • параллельной загрузки ресурсов из Cache Storage
  • снижения нагрузки на CDN Mapbox

На практике это влияет на:

  • скорость перемещения карты
  • время первого рендера
  • плавность zoom/pan операций

Обновление данных карты

Mapbox GL JS может обновлять источники данных в реальном времени. Service Worker должен учитывать этот факт, чтобы не блокировать свежие данные.

Комбинированная стратегия:

  • статические ресурсы → Cache First
  • динамические слои → Network First
  • тайлы → Stale-While-Revalidate

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