Одновременный вывод в несколько форматов

Rollup предоставляет возможность собирать один и тот же исходный код сразу в несколько форматов вывода. Это ключевая особенность, позволяющая одной сборкой обслуживать разные среды выполнения: браузер, Node.js, bundler-экосистемы и legacy-системы модульности. Поддержка параллельного формирования bundle делает Rollup удобным инструментом для библиотек, которые должны распространяться в универсальном виде.

Архитектура Rollup разделяет процесс сборки на два логических этапа:

  • анализ графа модулей (input)
  • генерация одного или нескольких выходных пакетов (output)

На этапе output конфигурация может содержать как один объект, так и массив объектов. Именно массив и является основным механизмом одновременного вывода в несколько форматов.

Каждый элемент массива описывает независимый bundle с собственными параметрами:

  • format
  • file или dir
  • name (для UMD/IIFE)
  • plugins (опционально, но может отличаться)
  • sourcemap
  • globals (для внешних зависимостей)

Базовая структура конфигурации с несколькими форматами

Ключевая возможность заключается в том, что input остаётся единым, а output становится множественным:

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/bundle.esm.js',
      format: 'es'
    },
    {
      file: 'dist/bundle.cjs.js',
      format: 'cjs'
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'MyLibrary'
    }
  ]
};

В этой конфигурации один граф модулей компилируется трижды в разные форматы без повторного запуска анализа исходников. Это важный момент: Rollup переиспользует построенный dependency graph, что снижает стоимость мульти-вывода.

Общая компиляция и переиспользование графа модулей

При использовании массива output Rollup выполняет следующие шаги:

  1. Строит единый граф зависимостей
  2. Применяет трансформации (plugins transform hooks)
  3. Генерирует отдельные chunks для каждого output
  4. Формирует финальные файлы в заданных форматах

Важно, что этап анализа происходит один раз, а генерация повторяется для каждого output-объекта.

Это даёт несколько преимуществ:

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

Различия конфигурации output для разных форматов

Каждый формат предъявляет собственные требования к конфигурации.

ES Modules (ESM)

{
  file: 'dist/index.esm.js',
  format: 'es'
}

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

  • сохраняется структура import/export
  • поддерживается tree-shaking на уровне потребителя
  • не требуется глобальное имя библиотеки
  • наиболее “чистый” формат

CommonJS

{
  file: 'dist/index.cjs.js',
  format: 'cjs'
}

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

  • используется require/module.exports
  • оптимален для Node.js
  • может включать дополнительную обвязку
  • иногда требует interop для ESM зависимостей

UMD

{
  file: 'dist/index.umd.js',
  format: 'umd',
  name: 'MyLibrary'
}

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

  • универсальный формат (AMD + CommonJS + global)
  • обязательно требует name
  • увеличенный размер bundle
  • часто используется для CDN-дистрибуции

Разделение output по окружениям

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

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/browser.esm.js',
      format: 'es'
    },
    {
      file: 'dist/node.cjs.js',
      format: 'cjs',
      external: ['fs', 'path']
    }
  ]
};

В таком подходе один и тот же код адаптируется под разные runtime-ограничения.

Использование разных плагинов для разных output

Rollup позволяет задавать отдельные plugin pipelines для каждого output-конфига. Это критически важно для случаев, когда:

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

Пример:

import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/lib.esm.js',
      format: 'es'
    },
    {
      file: 'dist/lib.umd.min.js',
      format: 'umd',
      name: 'Lib',
      plugins: [terser()]
    }
  ]
};

Здесь ES-модуль остаётся читаемым, а UMD версия дополнительно минифицируется.

Управление sourcemap в мульти-выводе

Sourcemap может быть настроен отдельно для каждого формата:

output: [
  {
    file: 'dist/a.esm.js',
    format: 'es',
    sourcemap: true
  },
  {
    file: 'dist/a.cjs.js',
    format: 'cjs',
    sourcemap: false
  }
]

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

  • включения sourcemap только для debug-версий
  • исключения sourcemap из production CDN сборок
  • оптимизации размера артефактов

Работа с именами глобальных переменных

При выводе в UMD или IIFE формат обязательно используется параметр name:

{
  file: 'dist/lib.umd.js',
  format: 'umd',
  name: 'LibCore'
}

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

  • одинаковое name для всех UMD/IIFE выходов одной библиотеки
  • избегание динамических или окруженных значений

Разделение output через dir вместо file

При сложных сборках используется dir, позволяющий формировать несколько файлов (chunks) для каждого формата:

output: [
  {
    dir: 'dist/esm',
    format: 'es'
  },
  {
    dir: 'dist/cjs',
    format: 'cjs'
  }
]

Это особенно важно при code splitting, когда Rollup создаёт несколько файлов вместо одного bundle.

Влияние code splitting на мульти-вывод

Если используется динамический import или manualChunks, структура output усложняется:

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

Пример:

output: [
  {
    dir: 'dist/es',
    format: 'es',
    manualChunks: {
      vendor: ['lodash']
    }
  },
  {
    dir: 'dist/cjs',
    format: 'cjs'
  }
]

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

Ограничения и особенности мульти-вывода

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

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

Особенно критичен момент с external:

external: ['react']

Если external задан в input-level конфигурации, он применяется ко всем output.

Типовые архитектуры использования

На практике мульти-вывод применяется в нескольких устойчивых схемах.

Библиотека общего назначения

  • ESM для bundler-экосистем
  • CJS для Node.js
  • UMD для CDN

SDK для браузера

  • ESM (основной)
  • minified IIFE (вторичный)

Monorepo-пакеты

  • ESM для внутреннего использования
  • CJS для совместимости с legacy пакетами

Производительность мульти-вывода

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

Факторы влияния:

  • количество форматов
  • использование минификации
  • сложность plugin pipeline
  • наличие code splitting

Оптимизация обычно достигается через:

  • разделение production/dev сборок
  • вынесение тяжелых плагинов в отдельные конфигурации
  • использование кеширования сборки

Практическая модель масштабирования output

При росте проекта конфигурация часто переходит от простого массива к структурированному подходу:

  • базовый config для shared options
  • factory-функции для output
  • разделение по environment

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

Поведение plugins в мульти-output режиме

Плагины могут вести себя по-разному в зависимости от стадии:

  • build hooks вызываются один раз
  • generateBundle вызывается для каждого output
  • writeBundle также повторяется

Это создаёт важный принцип: генерация артефактов является output-специфичной, а анализ — глобальным.

Некоторые плагины используют это для:

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

Итоговая модель мульти-вывода

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