Tree shaking

Tree shaking в экосистеме фронтенд-сборки означает автоматическое удаление неиспользуемого кода из итогового бандла. В случае с Leaflet этот механизм имеет специфические ограничения, связанные с историей библиотеки, её модульной структурой и характером побочных эффектов в геоинтерактивных API.

Leaflet изначально проектировался как компактная UMD-библиотека, ориентированная на глобальный объект L. Позднее появилась поддержка ES Modules, однако не все части API одинаково хорошо поддаются статическому анализу сборщиками.

Модульная архитектура и влияние на tree shaking

Классическая сборка Leaflet опирается на единый namespace:

import L from 'leaflet';

const map = L.map('map');

В этом случае сборщик воспринимает L как единый объект без возможности безопасно удалить части его содержимого. Причина — динамическая структура API и наличие побочных эффектов при инициализации модулей.

Современные версии предоставляют частичную ESM-структуру:

import { Map, TileLayer } from 'leaflet';

Однако не все внутренние классы и утилиты экспортируются как чистые функции без side effects, что ограничивает эффективность tree shaking.

Условия корректной работы tree shaking

Tree shaking в Leaflet становится предсказуемым только при выполнении ряда условий:

  • использование ES module-сборки библиотеки
  • наличие статических импортов без динамических выражений
  • отсутствие побочных эффектов в импортируемых модулях
  • поддержка sideEffects: false в конфигурации пакета (частично применимо)

Сборщики типа Vite, Rollup и Webpack (в production mode) используют статический анализ импортов:

import { Map, TileLayer, Marker } from 'leaflet';

Если код не использует Marker, он потенциально может быть исключён, но только при условии, что модуль Leaflet корректно размечен как tree-shakable.

ES Modules и их роль

ESM-версия Leaflet предоставляет более гранулярные точки входа:

import { Map } from 'leaflet';
import { TileLayer } from 'leaflet';

Такой стиль импорта улучшает анализ зависимостей, однако не гарантирует минимальный бандл. Причина заключается в том, что многие компоненты Leaflet имеют косвенные зависимости от core-инициализации.

Например, регистрация событий, классов и прототипных расширений выполняется при загрузке модуля, что делает часть кода «неудаляемой» с точки зрения tree shaking.

Сборка через Vite и Webpack

В современных сборках Leaflet чаще всего используется через ESM:

import { Map, TileLayer, Marker } from 'leaflet';

const map = new Map('map');
map.setView([51.505, -0.09], 13);

new TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')
  .addTo(map);

Vite использует Rollup под капотом, что обеспечивает более агрессивное удаление неиспользуемых экспортов. Webpack требует корректной настройки production mode:

module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
    sideEffects: true
  }
};

Однако даже при включённом usedExports Leaflet может частично сохранять неиспользуемые части API.

Побочные эффекты и CSS как фактор tree shaking

Leaflet содержит обязательные стилевые зависимости:

import 'leaflet/dist/leaflet.css';

CSS импорт не участвует в tree shaking, но влияет на финальный размер сборки через style injection или отдельный CSS chunk.

Кроме того, инициализация иконок:

import iconUrl from 'leaflet/dist/images/marker-icon.png';

создаёт дополнительные ассеты, которые не могут быть удалены tree shaking-механизмом, поскольку они не являются частью JS-графа зависимостей.

Ограничения tree shaking в Leaflet API

Некоторые конструкции Leaflet принципиально плохо поддаются статической оптимизации:

  • глобальные фабрики (L.map, L.tileLayer)
  • регистрация классов через внутренние реестры
  • динамическое расширение прототипов
  • плагины, подключаемые через side-effect imports

Пример:

import 'leaflet.markercluster';

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

Плагины и влияние на размер бандла

Экосистема Leaflet активно использует плагины, которые почти всегда нарушают tree shaking-модель:

import 'leaflet-draw';
import 'leaflet-routing-machine';

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

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

Паттерны использования ESM для уменьшения бандла

Наиболее устойчивые подходы к снижению размера сборки:

Изоляция используемых классов

import { Map, TileLayer } from 'leaflet';

Минимизация использования глобального L снижает вероятность подтягивания всего namespace.

Разделение точек входа

import { Map } from 'leaflet/src/map';
import { TileLayer } from 'leaflet/src/layer/tile';

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

Ленивая инициализация

let map;

function initMap() {
  const { Map } = require('leaflet');
  map = new Map('map');
}

Динамический require исключает tree shaking, но позволяет сегментировать загрузку.

Влияние bundler-стратегий

Tree shaking Leaflet напрямую зависит от стратегии сборки:

  • Rollup: наиболее точный анализ ESM-графа
  • Vite: быстрый pre-bundling и эффективное удаление неиспользуемого кода
  • Webpack: требует строгой настройки production-режима
  • Parcel: автоматическая оптимизация, но менее предсказуемая

Ключевым фактором остаётся корректная маркировка пакета как ESM-only или hybrid.

Типичные ошибки при попытке tree shaking Leaflet

Распространённые проблемы:

  • импорт всего namespace вместо точечных модулей
  • подключение плагинов без контроля side effects
  • использование старого UMD-синтаксиса L.*
  • отсутствие production-сборки
  • ожидание полного удаления core-кода Leaflet

Leaflet как геобиблиотека содержит базовый runtime, который практически всегда остаётся в финальном бандле, поскольку он необходим для работы карты и DOM-интеграции.