Bundle size оптимизация

В веб-картографии объём JavaScript-кода напрямую влияет на скорость загрузки страницы, время инициализации карты, производительность мобильных устройств и общий пользовательский опыт. Несмотря на то что Mapbox GL JS представляет собой высокопроизводительный движок визуализации карт на основе WebGL, его подключение может существенно увеличить итоговый размер клиентского приложения.

В современных SPA-приложениях на React, Vue, Angular или Svelte размер итогового бандла становится одним из ключевых показателей производительности. Особенно это заметно при использовании медленных сетей, мобильных устройств и сложных картографических интерфейсов.

Оптимизация размера бандла при работе с Mapbox GL JS включает несколько направлений:

  • выбор подходящей версии библиотеки;
  • правильная организация импортов;
  • ленивые загрузки;
  • разделение кода;
  • удаление неиспользуемых зависимостей;
  • оптимизация стилей и ресурсов;
  • контроль над дополнительными плагинами.

Из чего состоит размер Mapbox GL JS

После установки библиотеки:

npm install mapbox-gl

в приложение попадает несколько типов ресурсов:

  1. Основной JavaScript-код библиотеки.
  2. Web Worker для рендеринга и обработки тайлов.
  3. CSS-файл библиотеки.
  4. Дополнительные зависимости.
  5. Пользовательский код поверх Mapbox GL JS.

Типичная структура подключения выглядит следующим образом:

import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

Хотя библиотека уже оптимизирована разработчиками, её вес остаётся существенным по сравнению с обычными UI-компонентами.


Анализ размера бандла

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

Для проектов на Webpack часто используется пакет:

npm install webpack-bundle-analyzer --save-dev

Конфигурация:

const BundleAnalyzerPlugin =
    require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin()
    ]
};

После сборки:

npm run build

открывается интерактивная диаграмма состава бандла.

Для Vite применяется:

npm install rollup-plugin-visualizer --save-dev

Пример:

import { visualizer } from 'rollup-plugin-visualizer';

export default {
    plugins: [
        visualizer()
    ]
};

Анализ позволяет увидеть:

  • долю Mapbox GL JS;
  • размеры сторонних геопространственных библиотек;
  • объём собственных модулей;
  • дублирующиеся зависимости.

Использование Production-сборок

Одной из самых распространённых ошибок является тестирование размера приложения в режиме разработки.

Development-сборка содержит:

  • исходные имена переменных;
  • предупреждения;
  • дополнительные проверки;
  • карты исходников.

Для финального деплоя используется production-режим:

npm run build

Webpack автоматически включает:

  • минификацию;
  • tree shaking;
  • устранение мёртвого кода;
  • сжатие модулей.

Размер итогового бандла может уменьшиться в несколько раз.


Code Splitting

Карта часто используется только на отдельных страницах приложения.

Например:

  • экран поиска объектов;
  • страница мониторинга транспорта;
  • административная панель.

Нет смысла загружать Mapbox GL JS на главной странице сайта, если карта там отсутствует.

Динамический импорт

Вместо обычного импорта:

import mapboxgl from 'mapbox-gl';

используется:

const mapboxgl = await import('mapbox-gl');

или:

import('mapbox-gl').then(module => {
    const mapboxgl = module.default;
});

В результате создаётся отдельный чанк.

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


Ленивые компоненты в React

React предоставляет встроенный механизм динамической загрузки.

Компонент карты:

const MapPage = React.lazy(() => import('./MapPage'));

Использование:

<Suspense fallback={<div>Loading...</div>}>
    <MapPage />
</Suspense>

Преимущества:

  • уменьшение первого пакета загрузки;
  • более быстрый First Contentful Paint;
  • сокращение времени парсинга JavaScript.

Разделение маршрутов

Наиболее эффективная стратегия для крупных приложений — разделение кода по маршрутам.

Пример:

const Dashboard = lazy(() => import('./Dashboard'));
const MapView = lazy(() => import('./MapView'));
const Settings = lazy(() => import('./Settings'));

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


Исключение неиспользуемых библиотек

Часто основной рост размера приложения связан не с Mapbox GL JS, а с дополнительными пакетами.

Например:

npm install turf

Полный пакет Turf содержит десятки модулей.

Импорт:

import * as turf from '@turf/turf';

приводит к попаданию большого объёма кода в бандл.

Лучше использовать точечные импорты:

import buffer from '@turf/buffer';

или:

import distance from '@turf/distance';

В результате загружаются только необходимые алгоритмы.


Использование Tree Shaking

Tree Shaking удаляет код, который нигде не используется.

Неэффективный пример:

import * as turf from '@turf/turf';

Оптимизированный вариант:

import { distance } from '@turf/turf';

или:

import distance from '@turf/distance';

Чем меньше импортируется сущностей, тем эффективнее работает механизм удаления неиспользуемого кода.


Осторожность с UI-библиотеками

При создании картографических интерфейсов часто используются:

  • Material UI;
  • Ant Design;
  • PrimeReact;
  • Bootstrap-компоненты.

Необходимо избегать импортов вида:

import * as MUI from '@mui/material';

Предпочтительно:

import Button from '@mui/material/Button';

или:

import Dialog from '@mui/material/Dialog';

Подобная практика позволяет значительно уменьшить размер итогового пакета.


Оптимизация CSS

Mapbox GL JS требует подключения собственного CSS:

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

Удалять его нельзя, поскольку библиотека использует стили для:

  • контролов;
  • всплывающих окон;
  • атрибуции;
  • масштабирования.

Однако дополнительные стили проекта следует минимизировать.

Для этого используются:

cssnano

или встроенные механизмы сборщика.

Пример Vite:

export default {
    build: {
        cssMinify: true
    }
};

Удаление Source Maps из продакшена

Карты исходников значительно увеличивают объём публикуемых файлов.

Webpack:

module.exports = {
    devtool: false
};

Vite:

export default {
    build: {
        sourcemap: false
    }
};

Для конечных пользователей source maps обычно не нужны.


Отложенная загрузка карты

Даже после code splitting карта может загружаться слишком рано.

Распространённый подход — инициализация только после действия пользователя.

Например:

button.addEventListener('click', async () => {
    const mapboxgl = (await import('mapbox-gl')).default;

    new mapboxgl.Map({
        container: 'map'
    });
});

Преимущества:

  • уменьшение первоначального трафика;
  • ускорение отображения интерфейса;
  • сокращение потребления памяти.

Предзагрузка чанков

Иногда карта открывается почти на каждом сеансе работы.

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

Webpack:

import(
    /* webpackPrefetch: true */
    './MapPage'
);

Браузер скачает чанк заранее в свободное время.

В результате переход к карте станет практически мгновенным.


Оптимизация пользовательских слоёв

Крупные приложения часто содержат десятки файлов:

layers/
 ├─ roads.js
 ├─ buildings.js
 ├─ routes.js
 ├─ markers.js
 ├─ traffic.js

Если все слои импортируются одновременно:

import roads from './layers/roads';
import buildings from './layers/buildings';
import traffic from './layers/traffic';

размер стартового чанка увеличивается.

Лучше подключать функциональность по мере необходимости:

const traffic = await import('./layers/traffic');

Избежание дублирования геоданных

Нередко GeoJSON-файлы включаются непосредственно в код.

Пример:

import cities from './cities.geojson';

Если файл весит несколько мегабайт, он становится частью JavaScript-бандла.

Предпочтительнее загружать данные отдельно:

const response = await fetch('/data/cities.geojson');
const data = await response.json();

Преимущества:

  • уменьшение размера JavaScript;
  • возможность кеширования данных;
  • независимое обновление геоинформации.

Использование векторных тайлов вместо GeoJSON

Крупные наборы данных не должны полностью загружаться в браузер.

Неэффективный вариант:

map.addSource('objects', {
    type: 'geojson',
    data: bigGeoJson
});

Оптимизированный вариант:

map.addSource('objects', {
    type: 'vector',
    url: 'mapbox://tileset-id'
});

Векторные тайлы позволяют:

  • уменьшить сетевой трафик;
  • снизить объём памяти;
  • ускорить загрузку карты.

Контроль сторонних плагинов

Дополнительные расширения могут заметно увеличивать размер проекта:

mapbox-gl-draw
mapbox-gl-geocoder
deck.gl
mapbox-gl-directions

Каждый пакет следует оценивать отдельно.

Например, если редактирование геометрии используется только администраторами, модуль можно загружать динамически:

const MapboxDraw =
    (await import('@mapbox/mapbox-gl-draw')).default;

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


Оптимизация изображений и спрайтов

Размер приложения зависит не только от JavaScript.

Картографические проекты часто используют:

  • пользовательские иконки;
  • маркеры;
  • изображения слоёв;
  • символы интерфейса.

Полезные практики:

  • SVG вместо PNG там, где возможно;
  • сжатие изображений;
  • объединение иконок в спрайты;
  • использование современных форматов.

Пример загрузки:

map.loadImage('/icons/bus.webp', (error, image) => {
    if (error) {
        throw error;
    }

    map.addImage('bus', image);
});

Контроль зависимостей через package.json

Со временем проект накапливает большое количество пакетов.

Для анализа используются:

npm ls

или:

npm prune

Полезно регулярно проверять:

  • неиспользуемые зависимости;
  • устаревшие библиотеки;
  • дублирующиеся версии пакетов.

Каждая удалённая зависимость уменьшает размер итогового приложения.


Сжатие на сервере

Даже после минимизации JavaScript необходимо включать транспортное сжатие.

Наиболее распространённые варианты:

  • Gzip;
  • Brotli.

Пример конфигурации Nginx:

gzip on;
gzip_types application/javascript;

Современные браузеры поддерживают Brotli, обеспечивающий ещё более высокий коэффициент сжатия.

Для больших картографических приложений экономия может составлять сотни килобайт.


Использование современных сборщиков

Различные инструменты обеспечивают разную эффективность сборки.

Наиболее популярны:

Сборщик Особенности
Webpack Гибкая настройка
Vite Быстрая сборка и эффективное разделение кода
Rollup Отличный tree shaking
esbuild Высокая скорость
Rspack Современная высокопроизводительная альтернатива Webpack

Современные сборщики обычно генерируют более компактный код и лучше работают с динамическими импортами.


Практический чек-лист оптимизации

Минимальный уровень

  • Production-сборка.
  • Минификация CSS.
  • Gzip или Brotli.
  • Отключение source maps.

Средний уровень

  • Code splitting.
  • Lazy loading.
  • Tree shaking.
  • Точечные импорты Turf.
  • Загрузка GeoJSON через HTTP.

Максимальный уровень

  • Векторные тайлы вместо крупных GeoJSON.
  • Динамическая загрузка плагинов.
  • Предзагрузка нужных чанков.
  • Разделение слоёв по модулям.
  • Анализ бандла через Bundle Analyzer.
  • Регулярный аудит зависимостей.

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