Встраивание Rollup в скрипты сборки

Rollup как библиотека предоставляет не только CLI-инструмент, но и полноценный программный API, позволяющий интегрировать процесс сборки непосредственно в пользовательские скрипты. Такой подход используется в сложных сборочных системах, monorepo-конфигурациях, кастомных пайплайнах и CI-процессах, где требуется тонкий контроль над этапами трансформации модулей, генерацией бандлов и обработкой выходных артефактов.

Ключевым элементом программной интеграции выступает функция rollup, возвращающая объект сборки, через который выполняются генерация и запись бандлов.

Базовая структура сборочного скрипта

Минимальный сценарий использования Rollup в Node.js-скрипте строится вокруг двух этапов: создание бандла и его генерация или запись на диск.

import { rollup } from 'rollup';

const inputOptions = {
  input: 'src/index.js'
};

const outputOptions = {
  file: 'dist/bundle.js',
  format: 'esm'
};

async function build() {
  const bundle = await rollup(inputOptions);

  await bundle.write(outputOptions);

  await bundle.close();
}

build();

В этой модели:

  • inputOptions описывает входную точку и плагины
  • outputOptions определяет формат и место вывода
  • bundle.write() выполняет полную генерацию и запись
  • bundle.close() освобождает ресурсы (watcher, файловые дескрипторы)

Такой подход является базой для любого расширенного сценария сборки.

Разделение генерации и записи бандла

Rollup предоставляет два ключевых метода для вывода:

  • bundle.generate() — возвращает результат в памяти
  • bundle.write() — записывает результат на диск

Использование generate() позволяет внедрять дополнительные шаги обработки, например анализ кода или передачу результата в другой инструмент.

const bundle = await rollup(inputOptions);

const result = await bundle.generate(outputOptions);

for (const chunk of result.output) {
  console.log(chunk.code);
}

Такой подход полезен при построении кастомных пайплайнов, где Rollup выступает лишь одним из этапов трансформации.

Конфигурация inputOptions в скриптах

inputOptions определяет поведение компиляции и является центральным объектом управления сборкой.

Основные поля:

  • input — точка входа или массив точек входа
  • plugins — цепочка плагинов трансформации
  • external — зависимости, исключаемые из бандла
  • onwarn — обработчик предупреждений

Пример расширенной конфигурации:

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

const inputOptions = {
  input: ['src/main.js'],
  external: ['fs', 'path'],
  plugins: [
    resolve(),
    commonjs()
  ],
  onwarn(warning, warn) {
    if (warning.code === 'CIRCULAR_DEPENDENCY') return;
    warn(warning);
  }
};

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

Управление outputOptions в программной сборке

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

Основные поля:

  • file или dir — путь вывода
  • format — формат модуля (esm, cjs, iife, umd)
  • sourcemap — генерация source map
  • name — глобальное имя для IIFE/UMD
  • globals — маппинг внешних зависимостей

Пример динамического формирования:

const outputOptions = {
  file: process.env.BUILD_TARGET === 'prod'
    ? 'dist/app.min.js'
    : 'dist/app.js',
  format: 'esm',
  sourcemap: true
};

Такой подход позволяет связывать Rollup с переменными окружения и системами CI/CD без изменения самой конфигурации.

Использование Rollup как части build pipeline

Встраивание Rollup в скрипты сборки позволяет объединять несколько этапов:

  • транспиляция TypeScript или Babel
  • минификация
  • анализ зависимостей
  • генерация нескольких бандлов

Пример последовательного пайплайна:

import { rollup } from 'rollup';

async function buildAll() {
  const bundle = await rollup(inputOptions);

  await bundle.write({
    file: 'dist/app.esm.js',
    format: 'esm'
  });

  await bundle.write({
    file: 'dist/app.cjs.js',
    format: 'cjs'
  });

  await bundle.write({
    file: 'dist/app.iife.js',
    format: 'iife',
    name: 'MyApp'
  });

  await bundle.close();
}

buildAll();

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

Работа с watch-режимом в скриптах

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

import { watch } from 'rollup';

const watcher = watch({
  ...inputOptions,
  output: [outputOptions]
});

watcher.on('event', event => {
  if (event.code === 'START') {
    console.log('Сборка запущена');
  }

  if (event.code === 'BUNDLE_END') {
    console.log('Сборка завершена:', event.duration);
  }

  if (event.code === 'ERROR') {
    console.error(event.error);
  }
});

Watch-режим особенно важен в разработческих скриптах, где требуется мгновенная пересборка без перезапуска процесса Node.js.

Интеграция Rollup в кастомные Node.js инструменты

Встроенный API позволяет использовать Rollup как модуль внутри более крупных систем:

  • собственные task-runner’ы
  • CLI-инструменты
  • серверные сборочные сервисы
  • гибридные монорепозитории

Пример кастомного runner-а:

export async function buildProject(config) {
  const bundle = await rollup({
    input: config.entry,
    plugins: config.plugins
  });

  const outputs = Array.isArray(config.output)
    ? config.output
    : [config.output];

  for (const output of outputs) {
    await bundle.write(output);
  }

  await bundle.close();
}

Такой слой абстракции позволяет стандартизировать сборку между разными проектами без привязки к CLI.

Обработка ошибок в сборочных скриптах

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

try {
  const bundle = await rollup(inputOptions);
  await bundle.write(outputOptions);
  await bundle.close();
} catch (error) {
  console.error('Ошибка сборки:', error);
  process.exit(1);
}

В сложных системах часто добавляется дополнительная классификация ошибок:

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

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

Оптимизация повторных сборок

Встроенный API позволяет переиспользовать конфигурации и минимизировать перерасчёт графа зависимостей. В watch-режиме Rollup самостоятельно кэширует модули, но в кастомных сценариях можно управлять этим вручную через архитектуру скрипта.

Часто используется стратегия:

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

export async function rebuild() {
  if (!bundle) {
    bundle = await rollup(inputOptions);
  }

  await bundle.write(outputOptions);
}

Генерация нескольких окружений (dev/prod)

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

const isProd = process.env.NODE_ENV === 'production';

const inputOptions = {
  input: 'src/index.js',
  plugins: []
};

const outputOptions = {
  file: isProd ? 'dist/app.min.js' : 'dist/app.js',
  format: 'esm',
  sourcemap: !isProd
};

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

Использование Rollup в CI/CD сценариях

Встраивание Rollup в скрипты сборки позволяет легко интегрировать его в CI системы. Обычно создаётся отдельный Node.js-скрипт, который вызывается в пайплайне.

// build.js
import { rollup } from 'rollup';
import config from './rollup.config.js';

const bundle = await rollup(config.input);

await bundle.write(config.output);

await bundle.close();

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

Управление плагинами в программном API

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

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

const plugins = [
  resolve(),
  commonjs()
];

if (process.env.USE_BABEL) {
  plugins.push(babel({ babelHelpers: 'bundled' }));
}

const inputOptions = {
  input: 'src/index.js',
  plugins
};

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

Управление жизненным циклом bundle

Каждый вызов rollup() создаёт объект, который должен быть корректно завершён через close(). Это особенно важно при длительных процессах, watch-режимах и CI-сборках.

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

const bundle = await rollup(inputOptions);

try {
  await bundle.write(outputOptions);
} finally {
  await bundle.close();
}

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

Композиция сборочных сценариев

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

  • создание бандла
  • генерация выходов
  • постобработка (минификация, копирование, анализ)
  • публикация артефактов
async function build() {
  const bundle = await rollup(inputOptions);

  const { output } = await bundle.generate(outputOptions);

  await postProcess(output);

  await bundle.write(outputOptions);

  await bundle.close();
}

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