Tree shaking — это процесс удаления неиспользуемого кода из итоговой сборки JavaScript-приложения. Современные сборщики, такие как Webpack, Rollup, Vite и esbuild, анализируют зависимости проекта и исключают из финального бандла функции, классы и модули, которые не используются в приложении.
Для веб-картографических приложений размер бандла имеет особое значение. Mapbox GL JS содержит значительный объем функциональности: работу со стилями, слоями, геометрией, событиями, анимацией, источниками данных, поддержкой WebGL и множеством вспомогательных механизмов. Поэтому вопросы оптимизации размера итоговой сборки становятся важной частью разработки.
Основные цели tree shaking:
В крупных GIS-системах и веб-картах экономия даже нескольких сотен килобайт может существенно повлиять на скорость первого отображения интерфейса.
Tree shaking основан на статическом анализе импортов и экспортов ES Modules.
Например:
// utils.js
export function formatCoordinates() {
// ...
}
export function calculateDistance() {
// ...
}
Использование:
import { formatCoordinates } from './utils.js';
formatCoordinates();
Сборщик определяет, что функция calculateDistance()
нигде не используется, и удаляет её из итогового бандла.
Схематично процесс выглядит следующим образом:
Mapbox GL JS исторически разрабатывался как монолитная библиотека. Большая часть функциональности тесно связана между собой:
По этой причине классический tree shaking в отношении самой библиотеки работает ограниченно.
Типичное подключение выглядит так:
import mapboxgl from 'mapbox-gl';
После такого импорта сборщик рассматривает библиотеку как единый модуль и включает значительную часть её кода в сборку.
Для эффективного tree shaking необходимы следующие условия:
Сборщик должен видеть структуру экспортов:
export function addLayer() {}
export function removeLayer() {}
Код не должен выполнять действия при импорте:
console.log('module loaded');
или
window.globalConfig = {};
Функциональность должна быть разделена на отдельные части.
Mapbox GL JS содержит множество внутренних взаимосвязей:
Map
├── Style
├── SourceCache
├── Tile
├── Transform
├── Painter
└── Evented
Удаление одного компонента часто невозможно без нарушения работы остальных.
Хотя сама библиотека оптимизируется ограниченно, tree shaking прекрасно работает для окружающего приложения.
Например:
// mapHelpers.js
export function createMap() {
// ...
}
export function exportMapImage() {
// ...
}
export function calculateBounds() {
// ...
}
Использование:
import { createMap } from './mapHelpers.js';
createMap();
В итоговый бандл попадет только функция createMap().
Для максимальной эффективности необходимо использовать именованные импорты там, где это возможно.
Предпочтительно:
import { calculateDistance } from './geoUtils.js';
Менее эффективно:
import * as GeoUtils from './geoUtils.js';
Во втором случае сборщику сложнее определить реально используемые части модуля.
Tree shaking плохо работает с CommonJS-модулями.
Пример:
const geoUtils = require('./geoUtils');
или
module.exports = {
calculateDistance,
calculateArea
};
Подобная структура не предоставляет достаточно информации для статического анализа.
Современные проекты с Mapbox GL JS обычно используют ES Modules:
import mapboxgl from 'mapbox-gl';
что значительно улучшает возможности оптимизации.
Vite использует Rollup для production-сборки.
Пример настройки:
import { defineConfig } from 'vite';
export default defineConfig({
build: {
sourcemap: false
}
});
Во время сборки Rollup автоматически:
Запуск:
npm run build
После сборки можно заметить существенное сокращение размера собственного кода проекта.
Webpack поддерживает tree shaking начиная с версии 2.
Пример конфигурации:
module.exports = {
mode: 'production'
};
Production-режим автоматически включает:
Для дополнительного контроля используется параметр:
optimization: {
usedExports: true
}
В package.json можно указать отсутствие побочных эффектов:
{
"sideEffects": false
}
Это позволяет сборщику агрессивнее удалять неиспользуемые модули.
Пример:
import { createMap } from './map.js';
Если другие функции из файла не используются, они будут исключены из бандла.
Нельзя указывать:
{
"sideEffects": false
}
если модуль выполняет действия при загрузке.
Например:
mapboxgl.accessToken = TOKEN;
или
import './globalStyles.css';
В подобных случаях сборщик может ошибочно удалить необходимый код.
Корректный вариант:
{
"sideEffects": [
"*.css"
]
}
Tree shaking часто используется совместно с code splitting.
Вместо:
import mapboxgl from 'mapbox-gl';
можно применять динамическую загрузку:
const mapboxgl = await import('mapbox-gl');
или:
button.addEventListener('click', async () => {
const mapboxgl = await import('mapbox-gl');
createMap(mapboxgl.default);
});
В этом случае библиотека загружается только при необходимости.
Для оценки эффективности tree shaking применяются специальные инструменты.
Установка:
npm install webpack-bundle-analyzer --save-dev
Подключение:
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
После сборки открывается интерактивная диаграмма содержимого бандла.
Установка:
npm install rollup-plugin-visualizer --save-dev
Использование:
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [
visualizer()
]
};
Это позволяет увидеть, какую долю занимает Mapbox GL JS и какие модули проекта остаются после tree shaking.
import * as Helpers from './helpers.js';
Даже если используется одна функция:
Helpers.calculateDistance();
лучше писать:
import { calculateDistance } from './helpers.js';
export * from './geo.js';
export * from './map.js';
export * from './layers.js';
export * from './styles.js';
Подобная структура усложняет анализ зависимостей.
Более эффективен явный экспорт:
export { createMap } from './map.js';
export { calculateDistance } from './geo.js';
Плохо:
initializeApplication();
export function createMap() {}
Лучше:
export function initializeApplication() {}
export function createMap() {}
Инициализация вызывается явно:
initializeApplication();
Хорошо организованный проект с Mapbox GL JS обычно разделяет функциональность по модулям.
Структура:
src/
├── map/
│ ├── createMap.js
│ ├── controls.js
│ ├── layers.js
│ └── sources.js
│
├── geo/
│ ├── distance.js
│ ├── bounds.js
│ └── projection.js
│
└── ui/
├── sidebar.js
├── popup.js
└── toolbar.js
Использование:
import { createMap } from './map/createMap.js';
import { createPopup } from './ui/popup.js';
Неиспользуемые части автоматически исключаются сборщиком.
Дополнительные библиотеки экосистемы Mapbox могут влиять на размер бандла:
Вместо постоянного импорта:
import MapboxDraw from '@mapbox/mapbox-gl-draw';
можно применять ленивую загрузку:
const { default: MapboxDraw } =
await import('@mapbox/mapbox-gl-draw');
Это уменьшает первоначальный объем загружаемого кода.
| Подход | Размер начальной загрузки | Эффективность |
|---|---|---|
| Импорт всего приложения | Высокий | Низкая |
| Tree shaking | Средний | Высокая |
| Tree shaking + code splitting | Низкий | Очень высокая |
| Tree shaking + динамический импорт Mapbox-плагинов | Очень низкий | Максимальная |
Для проектов на Mapbox GL JS наиболее эффективной считается следующая стратегия:
sideEffects.В современных приложениях с Mapbox GL JS tree shaking редко приводит к значительному уменьшению размера самой картографической библиотеки, однако позволяет существенно сократить объем пользовательского кода и вспомогательных модулей. В сочетании с code splitting и динамическими импортами этот механизм становится одним из ключевых инструментов оптимизации производительности картографических веб-приложений.