Стратегии уменьшения размера финального бандла

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

Ключевое свойство, влияющее на размер выходного кода, — полная статичность структуры импортов. Любая динамика (require, условные импорты, побочные эффекты на уровне модулей) ухудшает возможности оптимизации.

Tree-shaking как основа минимизации

Механизм tree-shaking в Rollup опирается на анализ:

  • импортов и экспортов модулей
  • наличия побочных эффектов
  • достижимости кода из entry-point

Наиболее значимый фактор — корректная маркировка побочных эффектов.

sideEffects и влияние на вырезание кода

В package.json критически важен флаг:

{
  "sideEffects": false
}

или точечная настройка:

{
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}

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

Явные чистые функции и устранение мертвого кода

Rollup удаляет:

  • неиспользуемые экспортируемые функции
  • переменные, не участвующие в графе исполнения
  • условные блоки, вычислимые на этапе сборки

Особенно эффективно это работает при использовании const-структур и чистых функций без внешнего состояния.

Оптимизация структуры модулей

Переход на ESM-зависимости

Ключевой фактор уменьшения бандла — отказ от CommonJS зависимостей.

ESM позволяет:

  • статически анализировать зависимости
  • удалять неиспользуемые части библиотек
  • избегать оберток require/module.exports

При подключении CommonJS через @rollup/plugin-commonjs часто теряется часть оптимизаций, поэтому предпочтение всегда отдается ESM-версиям библиотек (например, lodash-es вместо lodash).

Разделение кода по функциональным зонам

Грамотная модульная структура снижает размер итогового бандла за счет локализации зависимостей.

Типичная ошибка — централизованные “barrel files” (index.js с массовыми re-export). Они часто ухудшают tree-shaking, если не настроены корректно.

Управление внешними зависимостями

external как инструмент исключения кода

Опция external в Rollup позволяет полностью исключить библиотеки из бандла:

external: ['react', 'react-dom']

Это критично для:

  • библиотек, используемых через CDN
  • серверных окружений (Node.js)
  • micro-frontend архитектур

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

Контроль транзитивных зависимостей

Многие крупные библиотеки тянут за собой цепочки зависимостей. Для уменьшения бандла используются:

  • анализ dependency tree
  • замена тяжелых пакетов легковесными аналогами
  • импорт только нужных функций (например, date-fns вместо moment)

Code splitting и динамические импорты

Разделение через dynamic import()

Rollup автоматически выделяет чанки при использовании:

import('./module.js')

Это позволяет:

  • уменьшить initial bundle size
  • загружать функциональность по требованию
  • оптимизировать критический путь загрузки

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

manualChunks для контроля структуры

Опция output.manualChunks позволяет управлять логикой разделения:

manualChunks(id) {
  if (id.includes('node_modules')) {
    return 'vendor';
  }
}

Более точная стратегия — разделение по доменным зонам:

  • UI
  • API
  • utils
  • vendor

Это снижает дублирование кода между чанками.

Минификация и пост-обработка

terser как основной инструмент сжатия

Rollup сам по себе не выполняет агрессивную минификацию. Используется @rollup/plugin-terser:

Ключевые оптимизации:

  • удаление whitespace
  • сокращение имен переменных
  • inline простых функций
  • устранение unreachable code

Dead code elimination на уровне минификатора

Некоторые оптимизации возможны только на уровне terser:

  • удаление console.log
  • устранение условий с константами
  • упрощение логических выражений

Оптимизация импорта библиотек

Импорт по частям

Вместо:

import _ from 'lodash';

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

import debounce from 'lodash/debounce';

или ESM-эквивалент:

import { debounce } from 'lodash-es';

Разница может составлять десятки килобайт.

Устранение barrel-эффекта

Barrel-файлы часто приводят к:

  • ухудшению tree-shaking
  • увеличению связности модулей
  • лишним импортам

Оптимальная стратегия — прямые импорты глубинных модулей.

Управление polyfills и compatibility layer

Подключение polyfills часто резко увеличивает размер бандла.

Стратегии уменьшения:

  • использование @babel/preset-env с useBuiltIns: 'usage'
  • точечное добавление polyfills
  • отказ от глобальных polyfills в пользу локальных импортов

Rollup-плагин @rollup/plugin-babel позволяет контролировать этот процесс, но ключевая оптимизация выполняется на уровне Babel.

Устранение побочных эффектов в модулях

Явная маркировка pure-кода

Terser поддерживает аннотации:

/*#__PURE__*/

Они позволяют безопасно удалять вызовы функций:

const instance = /*#__PURE__*/ createExpensiveObject();

Это особенно важно для React-компонентов и фабрик объектов.

Изоляция side-effect кода

Любой код, выполняющийся при импорте модуля, ухудшает tree-shaking:

// плохо
initTelemetry();

Лучше:

export function init() {
  initTelemetry();
}

Оптимизация работы с Node resolve и CommonJS

node-resolve конфигурация

@rollup/plugin-node-resolve влияет на выбор версии модулей:

  • browser
  • module
  • main

Приоритет module позволяет получить ESM-версию библиотеки, что уменьшает размер бандла.

commonjs трансформация

CommonJS обертка увеличивает код за счет:

  • преобразования require в ESM-совместимую структуру
  • добавления helper-функций
  • оберток модулей

Минимизация достигается сокращением числа CJS-зависимостей.

Уменьшение дублирования кода между чанками

Hoisting общих зависимостей

Rollup автоматически выделяет shared chunks, но эффективность зависит от структуры импортов.

Если модули импортируют одни и те же зависимости разными путями, возможны дубли.

Стратегия:

  • унификация точек входа
  • контроль re-export цепочек
  • использование consistent import paths

Inline vs separate assets

Встраивание мелких ресурсов

Мелкие файлы (иконки, JSON-конфиги) можно встраивать:

  • base64 encoding
  • JSON inline import

Но чрезмерное использование увеличивает размер JS и ухудшает кеширование.

Вынесение тяжелых ресурсов

Крупные ассеты должны быть внешними:

  • изображения
  • шрифты
  • большие JSON

Rollup plugins для этого позволяют контролировать порог инлайнинга.

Анализ итогового бандла

визуализация зависимостей

Плагины анализа позволяют выявить:

  • крупнейшие модули
  • дублирование зависимостей
  • неиспользуемые части библиотек

Типичная проблема — скрытые зависимости через транзитивные импорты.

выявление hot spots

Наибольший вклад в размер обычно дают:

  • UI-библиотеки
  • date/time библиотеки
  • utility collections
  • polyfills

Стратегии архитектурного уровня

разделение по уровням ответственности

Крупные приложения выигрывают от строгого разделения:

  • core (логика)
  • adapters (API)
  • ui (компоненты)
  • shared (утилиты)

Это уменьшает связность и улучшает tree-shaking.

lazy loading как фундаментальная стратегия

Перенос части функциональности в динамические чанки снижает initial bundle:

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

Управление конфигурацией Rollup

output.treeshake

Rollup позволяет тонко управлять поведением tree-shaking:

  • moduleSideEffects
  • propertyReadSideEffects
  • tryCatchDeoptimization

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

preserveModules

Режим preserveModules сохраняет структуру исходных файлов, что полезно для библиотек.

Плюсы:

  • максимальная предсказуемость tree-shaking у потребителя

Минусы:

  • рост количества файлов
  • возможный overhead импорта

Итоговая системная картина оптимизации

Минимизация бандла в Rollup достигается не отдельными настройками, а совокупностью решений:

  • строгий ESM-стек
  • контроль побочных эффектов
  • отказ от тяжелых зависимостей
  • корректное разделение чанков
  • ограничение polyfills
  • дисциплина импортов
  • минимизация runtime-оберток