Хук generateBundle

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

Фаза генерации бандла следует за этапом renderChunk, где каждый чанк уже получает финальный вид как текстовый JavaScript-код. После этого Rollup формирует единый объект результата, который передаётся в generateBundle.


Место generateBundle в жизненном цикле сборки

Жизненный цикл Rollup можно условно разделить на несколько этапов:

  • инициализация (options, buildStart)
  • построение графа модулей (resolveId, load, transform, moduleParsed)
  • генерация чанков (renderStart, renderChunk)
  • финализация бандла (generateBundle)
  • запись результата (writeBundle)

generateBundle находится между формированием итогового представления бандла и его записью. Важно понимать, что на этом этапе:

  • все чанки уже созданы
  • зависимости между модулями уже разрешены
  • код каждого чанка уже отрендерен
  • структура output сформирована, но ещё не зафиксирована в файловой системе

Именно это делает хук критически важным для постобработки результата.


Сигнатура и параметры хука

Хук вызывается один раз на каждую генерацию вывода:

generateBundle(options, bundle, isWrite)

options

Объект конфигурации выходного формата:

  • формат бандла (esm, cjs, iife и др.)
  • имя файла или директория вывода
  • флаги генерации
  • кастомные параметры output

Этот объект позволяет адаптировать поведение плагина под конкретный тип сборки.

bundle

Ключевой параметр хука — объект, содержащий все сгенерированные сущности бандла.

Структура:

  • ключи объекта — имена файлов (fileName)

  • значения — объекты типа:

    • chunk (JavaScript-чанк)
    • asset (не-JS ресурсы)

Пример:

{
  "main.js": {
    type: "chunk",
    code: "..."
  },
  "style.css": {
    type: "asset",
    source: "body { margin: 0; }"
  }
}

Через bundle можно:

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

isWrite

Булев флаг:

  • true — результат будет записан на диск (rollup -o)
  • false — используется только rollup.rollup() без записи

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


Возможности модификации bundle

Изменение содержимого чанков

Каждый chunk имеет поле code, которое можно изменять:

generateBundle(options, bundle) {
  for (const fileName of Object.keys(bundle)) {
    const file = bundle[fileName];

    if (file.type === 'chunk') {
      file.code += '\nconsole.log("postprocessed");';
    }
  }
}

Такой подход применяется для:

  • внедрения runtime-кода
  • добавления отладочной информации
  • постобработки транспилированного кода

Важно учитывать, что изменения происходят после всех этапов оптимизации.


Добавление новых ассетов

В generateBundle можно создавать новые файлы:

generateBundle(options, bundle) {
  this.emitFile({
    type: 'asset',
    fileName: 'meta.json',
    source: JSON.stringify({ build: Date.now() })
  });
}

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

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

Удаление файлов из bundle

Можно удалить ненужные элементы:

generateBundle(options, bundle) {
  for (const fileName of Object.keys(bundle)) {
    if (fileName.includes('debug')) {
      delete bundle[fileName];
    }
  }
}

Удаление применяется до записи и полностью исключает файл из результата.


Переименование файлов

Хотя прямого API для переименования нет, можно изменить fileName через модификацию объекта:

generateBundle(options, bundle) {
  const entry = bundle['main.js'];

  if (entry) {
    delete bundle['main.js'];
    entry.fileName = 'app.js';
    bundle['app.js'] = entry;
  }
}

Это требует аккуратности, так как ссылки между чанками могут зависеть от имен.


Взаимодействие с чанками и код-сплиттингом

В проектах с code splitting bundle содержит несколько типов чанков:

  • entry chunks
  • shared chunks
  • dynamic imports chunks

Хук позволяет:

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

Пример анализа:

generateBundle(options, bundle) {
  for (const file of Object.values(bundle)) {
    if (file.type === 'chunk') {
      console.log(file.fileName, file.imports, file.exports);
    }
  }
}

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

  • построения карт зависимостей
  • анализа оптимальности split-point’ов
  • генерации отчётов

Ограничения хука generateBundle

Несмотря на широкие возможности, существуют ограничения:

1. Отсутствие доступа к модульному графу

На этом этапе недоступны:

  • исходные модули
  • transform-результаты отдельных модулей
  • промежуточные AST

Доступен только финальный bundle.


2. Невозможность повлиять на код чанков структурно

Изменение code — это строковая операция. Нет доступа к AST или синтаксическим структурам.


3. Опасность нарушения целостности бандла

Некорректные изменения могут привести к:

  • поломке импортов между чанками
  • конфликтам имен
  • некорректной работе runtime Rollup

Типичные сценарии использования

Вставка глобальных баннеров или runtime-кода

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

  • лицензий
  • предупреждений
  • polyfill-инициализации

Генерация метаданных сборки

generateBundle(options, bundle) {
  const meta = Object.keys(bundle).map(name => ({
    name,
    size: bundle[name].type === 'chunk'
      ? bundle[name].code.length
      : bundle[name].source.length
  }));

  this.emitFile({
    type: 'asset',
    fileName: 'bundle-meta.json',
    source: JSON.stringify(meta, null, 2)
  });
}

Интеграция с внешними системами сборки

Хук часто используется для:

  • CI/CD пайплайнов
  • систем версионирования
  • генерации sourcemap отчётов
  • анализа размера бандла

Отличие generateBundle от writeBundle

Ключевое различие:

  • generateBundle — модификация результата до записи
  • writeBundle — постобработка уже записанных файлов

generateBundle предпочтителен, когда требуется:

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

writeBundle используется, когда нужно:

  • работать с файловой системой
  • отправлять файлы во внешние сервисы
  • логировать результат записи

Контекст выполнения и this

Внутри generateBundle доступен контекст плагина:

  • this.emitFile
  • this.getFileName
  • this.error

Пример генерации связанного ассета:

generateBundle() {
  const fileRef = this.emitFile({
    type: 'asset',
    name: 'runtime-info',
    source: 'init'
  });

  const fileName = this.getFileName(fileRef);
}

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


Работа с производительностью

При большом количестве чанков важно учитывать:

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

Оптимизация:

  • избегать лишних копирований строк
  • минимизировать операции удаления/переназначения
  • использовать фильтрацию по типу (chunk / asset)

Практическая модель применения в архитектуре плагинов

В архитектуре Rollup-плагинов generateBundle часто выступает как слой агрегации:

  • трансформации уже завершены
  • структура зафиксирована
  • остаётся только финальная композиция результата

Типичные роли:

  • генератор манифестов
  • постпроцессор сборки
  • адаптер под сторонние форматы (например, интеграция с CDN)
  • инструмент аудита и анализа

Взаимосвязь с системой эмиссии файлов

generateBundle тесно связан с механизмом emitFile:

  • emitFile регистрирует новый файл в сборке
  • Rollup добавляет его в bundle
  • затем он становится доступен для модификации

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


Поведение в watch-режиме

При использовании watch-режима:

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

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


Итоговая роль в архитектуре Rollup

generateBundle является финальной точкой программного вмешательства в структуру результата сборки, где:

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

Это делает хук инструментом уровня «финальной сборки продукта», а не этапом трансформации кода.