Bundling vs unbundling: когда оставлять модули раздельными

Bundling в Rollup представляет собой процесс объединения множества модулей JavaScript в один или несколько итоговых файлов, тогда как unbundling — осознанное сохранение части модулей раздельными для более гибкого распределения ответственности между сборкой и окружением исполнения. Баланс между этими подходами определяет архитектуру итоговой библиотеки, её совместимость, размер и поведение при интеграции в разные среды.

Rollup строит граф зависимостей начиная с входной точки и последовательно объединяет импортируемые модули в единый результат. Основная цель такого подхода — создание компактного и оптимизированного кода за счёт устранения лишних связей и применения tree-shaking.

Ключевые особенности bundling:

  • статический анализ import/export
  • удаление неиспользуемого кода (dead code elimination)
  • инлайнинг небольших модулей
  • оптимизация структуры вызовов

Rollup исторически ориентирован на библиотеки, где важна чистота ESM-графа и минимальный runtime-overhead.

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

Unbundling как архитектурный приём

Unbundling в Rollup означает сознательное разделение кода на части, которые не попадают в финальный бандл, а остаются отдельными модулями, подключаемыми через import во время выполнения или через систему модульной загрузки окружения.

Основные сценарии применения:

  • внешние зависимости (React, Vue, lodash)
  • peerDependencies в библиотеках
  • плагины и расширения
  • крупные подсистемы с независимым жизненным циклом
  • интеграции с окружением (Node.js, браузер, SSR)

Механизм реализуется через external, который исключает модули из графа сборки Rollup:

export default {
  input: 'src/index.js',
  external: ['react', 'react-dom']
}

В результате Rollup сохраняет import-выражения без инлайнинга кода.

Граница между bundling и unbundling

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

Полное bundling

Подходит для:

  • CLI-инструментов
  • standalone-библиотек без зависимостей
  • утилит с минимальной поверхностью API

Характерные свойства:

  • один или несколько файлов сборки
  • отсутствуют runtime-зависимости (кроме системных)
  • максимальная переносимость

Недостаток — увеличение размера и возможное дублирование зависимостей в конечном приложении.

Частичное bundling

Наиболее распространённый вариант для библиотек.

Сборка включает:

  • собственный код библиотеки
  • утилитарные модули
  • трансформации и хелперы

И исключает:

  • framework-зависимости
  • большие peer-библиотеки
  • платформенные API

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

Полное unbundling

Редкий сценарий, при котором библиотека фактически становится набором ESM-модулей без агрегации.

Особенности:

  • сохраняется структура исходников
  • сборка минимальна или отсутствует
  • ответственность за оптимизацию передаётся сборщику потребителя

Используется в:

  • дизайн-системах
  • модульных фреймворках
  • экосистемах плагинов

Влияние external на архитектуру

Конфигурация external является центральным механизмом unbundling в Rollup. Она определяет, какие зависимости:

  • остаются импортами
  • исключаются из графа сборки
  • не участвуют в tree-shaking внутри бандла

Типовые стратегии:

Явное перечисление

external: ['react', 'react/jsx-runtime']

Используется при фиксированном наборе зависимостей.

Функциональное определение

external: id => id.startsWith('react')

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

Regex-подход

external: [/^react/, /^@babel/]

Применяется в крупных библиотеках с множеством peer-зависимостей.

Peer dependencies и unbundling

PeerDependencies усиливают концепцию unbundling, фиксируя принцип: библиотека не должна поставлять собственную версию критических зависимостей.

Причины:

  • предотвращение дублирования React/Vue в приложении
  • избежание конфликтов версий
  • уменьшение итогового bundle size
  • контроль за жизненным циклом зависимостей со стороны приложения

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

Tree-shaking и влияние границ модулей

Гранулярность модулей напрямую влияет на эффективность tree-shaking.

При агрессивном bundling:

  • код объединяется в крупные единицы
  • ухудшается точность анализа используемых экспортов
  • возможны ложные включения кода

При unbundling:

  • сохраняется модульная структура ESM
  • tree-shaking выполняется на уровне потребителя
  • повышается точность удаления неиспользуемого кода

Rollup эффективнее всего работает, когда модули мелкие и чистые, без побочных эффектов.

Code splitting и гибридные стратегии

Rollup поддерживает разделение бандла на чанки, что создаёт промежуточную модель между bundling и unbundling.

Пример динамического импорта:

import('./feature.js')

Результат:

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

Это особенно важно для библиотек с расширяемой функциональностью.

Библиотеки и приложения: разные стратегии сборки

Поведение bundling/unbundling сильно зависит от типа проекта.

Библиотеки

Основные цели:

  • минимальный размер
  • совместимость с экосистемой
  • корректная работа peerDependencies

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

  • частичный bundling
  • строгий external
  • ESM + CJS dual build

Приложения

Основные цели:

  • скорость загрузки
  • оптимизация runtime
  • кэширование чанков

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

  • агрессивный bundling
  • code splitting
  • инлайнинг зависимостей

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

Dual package и влияние на границы сборки

Dual package (ESM + CommonJS) усиливает необходимость точного контроля bundling.

Типичная структура:

  • ESM build: для современных сборщиков
  • CJS build: для Node.js-экосистемы

При этом:

  • внешние зависимости должны быть синхронизированы
  • external конфигурация обязана быть идентичной
  • различия в резолве модулей могут влиять на tree-shaking

Ошибки смешивания bundling и unbundling

На практике проблемы возникают при нарушении границ:

  • случайный инлайнинг peerDependencies
  • отсутствие external для framework-библиотек
  • дублирование React в финальном приложении
  • потеря tree-shaking из-за CJS-модулей
  • избыточное дробление без выгоды для загрузки

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

Архитектурные принципы выбора стратегии

Выбор между bundling и unbundling определяется несколькими факторами:

  • характер зависимости (внутренняя или внешняя)
  • размер модуля и частота использования
  • стабильность API
  • ответственность за выполнение кода
  • требования к tree-shaking
  • целевая среда исполнения

Стабильные утилиты и ядро библиотеки чаще выигрывают от bundling, тогда как интеграционные слои и внешние зависимости требуют unbundling для сохранения гибкости и предотвращения дублирования.

Граница между этими подходами формирует не просто конфигурацию Rollup, а структуру всей библиотеки и способ её включения в экосистему.