Оптимизация общих зависимостей

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

В отличие от бандлеров с агрессивным рантайм-чанкингом, Rollup ориентирован на статический анализ. Это означает, что решение о том, как разделять или объединять общие зависимости, принимается на этапе сборки, а не выполнения.

Как возникает дублирование зависимостей

Дублирование модулей появляется в нескольких типичных ситуациях:

  • разные входные точки (multi-entry build)
  • динамические импорты без корректной группировки
  • отсутствие явной стратегии chunking
  • несовместимость форматов зависимостей (CommonJS через плагины)
  • раздельная обработка библиотек и приложения без общей схемы кеширования

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

Статическое связывание и влияние tree-shaking

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

Это приводит к ситуации, когда:

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

При слабой модульной структуре это превращается в скрытое дублирование логики.

Базовые стратегии оптимизации общих зависимостей

Оптимизация начинается с выстраивания явной политики формирования чанков. Rollup не навязывает фиксированную модель vendor/app, поэтому её необходимо задавать через конфигурацию.

Основные подходы:

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

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

Использование manualChunks для контроля общих модулей

Механизм output.manualChunks является основным инструментом управления общими зависимостями.

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

export default {
  output: {
    manualChunks(id) {
      if (id.includes('node_modules')) {
        return 'vendor';
      }
    }
  }
};

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

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

manualChunks(id) {
  if (id.includes('node_modules')) {
    if (id.includes('react')) return 'react-vendor';
    if (id.includes('lodash')) return 'utils-vendor';
    return 'vendor';
  }
}

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

Группировка по доменным областям

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

Практика группировки по доменам:

  • auth-модули
  • работа с API
  • UI-компоненты
  • утилиты

Пример:

manualChunks(id) {
  if (id.includes('/src/api/')) return 'api';
  if (id.includes('/src/ui/')) return 'ui';
  if (id.includes('/src/auth/')) return 'auth';
}

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

Динамические импорты и влияние на shared chunks

import() в Rollup создаёт отдельные чанки, которые становятся естественным механизмом разделения кода. Однако без контроля они могут приводить к разрастанию числа общих зависимостей.

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

Оптимизация заключается в том, чтобы:

  • выносить тяжёлые зависимости в статические shared chunks
  • избегать дублирования vendor-кода внутри динамических модулей
  • контролировать пересечения через manualChunks

preserveModules и влияние на общие зависимости

Опция preserveModules изменяет стратегию сборки: вместо объединения модулей в чанки сохраняется структура исходных файлов.

При включении:

  • общие зависимости не агрегируются автоматически
  • каждый модуль остаётся отдельным файлом
  • возрастает важность внешнего кеширования и HTTP/2

Оптимизация в этом режиме смещается в сторону:

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

Оптимизация через разделение vendor и application layers

Один из устойчивых подходов — разделение кода на два уровня:

  • vendor layer (сторонние зависимости)
  • application layer (бизнес-логика)

Такое разделение снижает вероятность дублирования, поскольку vendor-чанк становится стабильной точкой переиспользования.

При корректной настройке:

  • vendor меняется редко
  • application может пересобираться часто
  • кеш браузера эффективно работает на уровне зависимостей

Кэширование и стабильность идентификаторов модулей

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

Факторы, влияющие на стабильность:

  • порядок импортов
  • алиасы путей
  • плагины трансформации
  • разрешение модулей через Node resolution

Оптимизация требует минимизации нестабильных трансформаций и унификации путей.

Предотвращение скрытых пересечений зависимостей

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

Причины:

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

Контроль осуществляется через:

  • выравнивание версий зависимостей
  • использование alias для унификации путей
  • явное определение shared chunks

Практика стабилизации графа зависимостей

Стабильность графа важнее агрессивного дробления кода. При оптимизации общих зависимостей важно добиваться предсказуемости структуры:

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

Это позволяет избежать эффекта «разбухания» бандла при небольших изменениях кода.

Баланс между количеством чанков и переиспользованием

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

Оптимизация общих зависимостей сводится к балансу:

  • минимизация дублирования кода
  • контроль количества общих чанков
  • снижение пересечений между entry points
  • сохранение логической изоляции модулей

Выбор стратегии зависит от характера приложения: библиотека, SPA или многопоточный multi-entry проект требуют разных схем группировки зависимостей.