Rollup

Современные JavaScript-библиотеки требуют предсказуемой, компактной и оптимизированной системы сборки. Rollup ориентирован на создание библиотек, где ключевым фактором становится не столько разделение кода на чанки, сколько получение максимально чистого и эффективного итогового бандла. В контексте проектов, использующих Leaflet, это особенно важно: библиотека должна оставаться легковесной, совместимой с различными форматами модулей и не дублировать зависимости в конечном пакете.

Rollup работает на основе ES Modules и выполняет статический анализ импортов, формируя граф зависимостей. Такой подход позволяет выполнять tree-shaking — удаление неиспользуемого кода на этапе сборки, что критично для библиотек визуализации и картографии.

Модель модульной сборки и tree-shaking

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

Tree-shaking реализуется за счёт анализа:

  • import и export на этапе парсинга AST
  • отсутствия побочных эффектов в модулях
  • явной декларации зависимостей

Для библиотек наподобие Leaflet это особенно важно: даже небольшое сокращение итогового бандла влияет на скорость загрузки карты и инициализации интерфейса.

Ключевым условием эффективного tree-shaking является корректная настройка поля:

{
  "sideEffects": false
}

в package.json, если проект не содержит побочных эффектов при импорте модулей.

Форматы вывода: ESM, CJS и UMD

Rollup позволяет формировать несколько типов сборок из одного исходного кода:

  • ESM (ES Modules) — основной формат для современных сборщиков и браузеров
  • CJS (CommonJS) — для Node.js-окружения
  • UMD (Universal Module Definition) — универсальный формат для подключения через <script>

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

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

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/library.esm.js',
      format: 'es',
      sourcemap: true
    },
    {
      file: 'dist/library.cjs.js',
      format: 'cjs',
      exports: 'named',
      sourcemap: true
    },
    {
      file: 'dist/library.umd.js',
      format: 'umd',
      name: 'MyMapLibrary',
      sourcemap: true
    }
  ]
};

Работа с внешними зависимостями (external)

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

Rollup позволяет исключать зависимости:

export default {
  input: 'src/index.js',
  external: ['leaflet'],
  output: {
    file: 'dist/bundle.js',
    format: 'esm'
  }
};

В таком случае импорт Leaflet остаётся внешним:

import L from 'leaflet';

export function createMap(id) {
  return L.map(id).setView([51.505, -0.09], 13);
}

При сборке библиотек поверх Leaflet это стандартная практика: Leaflet объявляется peer dependency.

Подключение плагинов Rollup

Экосистема Rollup основана на плагинах, которые расширяют базовую функциональность сборщика.

node-resolve

Позволяет находить модули в node_modules:

import resolve from '@rollup/plugin-node-resolve';

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

Без этого плагина Rollup не сможет корректно обрабатывать сторонние зависимости.

commonjs

Leaflet и множество вспомогательных пакетов могут быть написаны в формате CommonJS. Для их поддержки используется:

import commonjs from '@rollup/plugin-commonjs';

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

Этот плагин преобразует require и module.exports в ES Module-совместимый код.

terser

Минификация итогового бандла выполняется через terser:

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

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

Минификация особенно важна для UMD-сборок, которые часто загружаются напрямую в браузере.

Сборка библиотеки поверх Leaflet

При создании расширений для Leaflet структура проекта обычно разделяется на:

  • ядро библиотеки
  • плагины визуализации
  • утилиты обработки данных
  • слой интеграции с Leaflet API

Пример структуры:

src/
  index.js
  core/
    mapFactory.js
  layers/
    heatmapLayer.js
  utils/
    geoUtils.js

Главный модуль:

import L from 'leaflet';
import { createHeatLayer } from './layers/heatmapLayer.js';

export function initAdvancedMap(container, options = {}) {
  const map = L.map(container, options);

  const heatLayer = createHeatLayer(options.data);
  heatLayer.addTo(map);

  return map;
}

Конфигурация Rollup для библиотек на Leaflet

Полная конфигурация обычно включает несколько уровней оптимизации:

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';

const isProd = process.env.NODE_ENV === 'production';

export default {
  input: 'src/index.js',
  external: ['leaflet'],
  output: [
    {
      file: 'dist/library.esm.js',
      format: 'es',
      sourcemap: true
    },
    {
      file: 'dist/library.umd.js',
      format: 'umd',
      name: 'AdvancedLeaflet',
      globals: {
        leaflet: 'L'
      },
      sourcemap: true
    }
  ],
  plugins: [
    resolve(),
    commonjs(),
    isProd && terser()
  ]
};

Ключевой момент — корректное сопоставление globals, которое необходимо для UMD-сборок при подключении Leaflet через CDN.

Sourcemaps и отладка

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

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

  • inline sourcemaps
  • отдельные .map файлы
  • скрытые sourcemaps
output: {
  sourcemap: true
}

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

Code splitting и ограничения Rollup

Rollup поддерживает code splitting только в ESM-режиме. Однако для библиотек, интегрирующих Leaflet, часто предпочтительнее единый бандл.

Причины:

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

Поэтому большинство Leaflet-надстроек используют single-bundle подход.

Плагинная архитектура сборки

Сложные геопространственные библиотеки часто требуют кастомной обработки данных: GeoJSON, тайлы, векторные слои.

Rollup позволяет добавлять пользовательские плагины:

function geoJsonOptimizer() {
  return {
    name: 'geojson-optimizer',
    transform(code, id) {
      if (!id.endsWith('.geojson')) return null;

      const optimized = JSON.stringify(JSON.parse(code));
      return {
        code: `export default ${optimized};`,
        map: null
      };
    }
  };
}

Такая обработка уменьшает размер данных и ускоряет загрузку карт.

Управление зависимостями Leaflet-плагинов

Leaflet-плагины часто подключаются как независимые модули. Rollup позволяет управлять их интеграцией через peerDependencies.

package.json:

{
  "peerDependencies": {
    "leaflet": "^1.9.0"
  }
}

Это предотвращает дублирование Leaflet в итоговой сборке.

Оптимизация итогового бандла

Основные стратегии оптимизации:

  • исключение Leaflet из сборки (external)
  • минимизация через terser
  • удаление неиспользуемых функций через tree-shaking
  • разделение dev/prod конфигураций
  • использование ESM как базового формата

Дополнительно можно применять анализ бандла:

rollup -c --configPlugin visualizer

Это позволяет оценить вклад каждого модуля в итоговый размер.

Работа с несколькими входными точками

В библиотеке может быть несколько entry points:

export default {
  input: {
    main: 'src/index.js',
    layers: 'src/layers/index.js',
    utils: 'src/utils/index.js'
  }
};

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

Интеграция с современным JavaScript-стеком

Rollup часто используется совместно с:

  • TypeScript (через @rollup/plugin-typescript)
  • Babel (для поддержки старых браузеров)
  • PostCSS (для стилизации UI-контролов карты)

Пример TypeScript-интеграции:

import typescript from '@rollup/plugin-typescript';

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

Режим разработки

В dev-режиме Rollup обычно используется с watch:

rollup -c -w

Это позволяет автоматически пересобирать библиотеку при изменениях. Для Leaflet-расширений это важно, так как визуальные изменения слоёв требуют постоянной проверки в браузере.

Дополнительно используется локальный сервер и live reload, но сама сборка остаётся детерминированной и быстрой благодаря отсутствию избыточной упаковки модулей.

Архитектурные особенности сборки картографических библиотек

При проектировании сборки поверх Leaflet учитываются специфические требования:

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

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