Инъекция кода через banner и footer

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

На этапе генерации Rollup формирует единый текстовый результат, объединяя модули согласно графу зависимостей и применяя трансформации плагинов. После завершения композиции модулей в итоговый код выполняется постобработка, в рамках которой и применяются banner и footer.

banner вставляется в самое начало результирующего файла, до любого сгенерированного кода модулей.

footer добавляется в самый конец бандла, после всех модульных выражений и экспортов.

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

Синтаксис и базовое использование

В конфигурации Rollup опции задаются в объекте output:

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife',
    banner: '/* библиотека: core-utils */',
    footer: '/* конец бандла */'
  }
};

В результате сформированный файл будет иметь следующую структуру:

/* библиотека: core-utils */
(function () {
  // сгенерированный код модулей
})();
/* конец бандла */

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

Динамическая генерация через функцию

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

export default {
  output: {
    file: 'dist/bundle.js',
    format: 'esm',
    banner: (chunkInfo) => {
      return `/* build: ${chunkInfo.fileName} */`;
    },
    footer: (chunkInfo) => {
      return `/* size: ${chunkInfo.modules.length} modules */`;
    }
  }
};

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

Функции вызываются на этапе генерации чанков, когда структура уже полностью сформирована. Это означает:

  • граф модулей уже зафиксирован
  • tree-shaking завершён
  • код модулей уже преобразован в строковое представление
  • изменения в этих функциях не влияют на сам граф зависимостей

Таким образом, banner и footer являются исключительно постгенерационным слоем.

Область применения banner

Основное назначение banner связано с добавлением информации, которая должна быть первой в файле.

Лицензии и юридическая информация

Часто используется для вставки лицензий:

banner: `/*!
 * Project Name
 * (c) Company
 * License: MIT
 */`

Такая информация должна располагаться до любого исполняемого кода, чтобы соответствовать требованиям распространения библиотек.

Глобальные полифиллы

В редких случаях banner используется для внедрения минимальных патчей окружения:

banner: `;if (typeof window === 'undefined') { global.window = global; }`

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

Метки сборки

Banner может использоваться для внедрения метаданных:

banner: `/* build time: ${new Date().toISOString()} */`

Такая информация помогает при отладке и трассировке версий.

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

Замыкание или финальная инициализация

В формате IIFE footer может использоваться для финальных вызовов:

footer: `;initApp();`

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

Дополнительные экспорты или патчи

В некоторых случаях footer применяется для модификации глобального состояния:

footer: `;window.__BUNDLE_LOADED__ = true;`

Это позволяет внешним системам отслеживать завершение загрузки бандла.

Совместимость с устаревшими окружениями

Footer может содержать shim-логики, которые должны быть доступны после основного кода:

footer: `
if (!Array.prototype.flat) {
  Array.prototype.flat = function() { /* polyfill */ };
}
`

Влияние на форматы вывода

Поведение banner и footer зависит от формата сборки.

IIFE и UMD

В этих форматах код оборачивается в функцию или универсальную обёртку, поэтому:

  • banner располагается перед IIFE-обёрткой
  • footer располагается после закрытия обёртки

Это делает их внешними по отношению к модульной системе.

ESM

В формате ES modules:

  • banner становится первым выражением в модуле
  • footer становится последним выражением

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

CJS

В CommonJS:

  • banner размещается перед module.exports
  • footer — после всех экспортных присваиваний

Ограничения и потенциальные риски

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

Отсутствие контекстной осведомлённости

Код в banner/footer не имеет доступа к API плагинов и не может взаимодействовать с графом модулей. Это исключает возможность:

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

Риск конфликтов синтаксиса

Поскольку вставка выполняется как строка, некорректный синтаксис может нарушить весь бандл:

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

Особенно критично это для footer, так как он располагается в конце и может влиять на завершение выражений.

Потенциальное влияние на минификацию

Хотя banner/footer обычно исключаются из tree-shaking, они могут:

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

Практика использования в сложных сборках

В реальных проектах banner и footer часто применяются совместно с другими механизмами Rollup.

Интеграция с режимами окружения

output: {
  banner: process.env.NODE_ENV === 'production'
    ? '/* prod build */'
    : '/* dev build */'
}

Комбинация с несколькими чанками

При code splitting каждая часть бандла может получать собственный banner/footer через chunk-level конфигурацию:

banner: (chunk) => `/* chunk: ${chunk.name} */`

Это позволяет идентифицировать отдельные части сборки.

Использование в библиотечных пакетах

В библиотечных сборках banner часто содержит:

  • имя пакета
  • версию
  • лицензию

Footer может содержать маркеры инициализации или совместимости с CDN-окружениями.

Поведение при sourcemap

banner и footer не участвуют в генерации sourcemap как отдельные модули, однако их длина учитывается при расчёте смещений. Это означает, что:

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

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

Хотя banner и footer не проходят через transform-хуки, их наличие может косвенно влиять на плагины, работающие с финальным кодом:

  • плагин output.generateBundle видит уже модифицированный код
  • плагины минификации получают banner/footer как часть строки
  • анализаторы кода учитывают их при постобработке

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

Стратегии минимизации побочных эффектов

При активном использовании banner и footer в сложных сборках применяются определённые ограничения:

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

Такая практика снижает риск нарушения целостности итогового бандла и упрощает сопровождение сборки.