Измерение времени сборки

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

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

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

Дополнительно важно учитывать режимы запуска:

  • однократная сборка (rollup -c)
  • watch-режим (rollup -c -w)
  • сборка с минимизированием и source maps

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

Измерение через Node.js API

При использовании программного API Rollup контроль времени становится более гибким. Базовый подход — измерение общего времени выполнения через высокоточные таймеры Node.js:

import { rollup } from 'rollup';
import { performance } from 'node:perf_hooks';

const start = performance.now();

const bundle = await rollup({
  input: 'src/index.js'
});

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

const end = performance.now();

console.log(`Build time: ${end - start} ms`);

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

Более точный контроль достигается разбиением по этапам:

const t1 = performance.now();
const bundle = await rollup(inputOptions);
const t2 = performance.now();

await bundle.generate(outputOptions);
const t3 = performance.now();

await bundle.write(outputOptions);
const t4 = performance.now();

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

Плагинное профилирование

Rollup-плагины являются основной причиной нестабильного времени сборки. Каждый плагин может участвовать в разных фазах:

  • buildStart
  • resolveId
  • load
  • transform
  • generateBundle

Для измерения влияния конкретного плагина используется ручное профилирование внутри его хуков:

export function timingPlugin() {
  return {
    name: 'timing-plugin',

    transform(code, id) {
      const start = performance.now();

      // имитация или реальная трансформация
      const result = code;

      const end = performance.now();
      console.log(`transform ${id}: ${end - start} ms`);

      return result;
    }
  };
}

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

Анализ watch-режима и инкрементальных сборок

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

  • первая сборка (cold build)
  • последующие инкрементальные пересборки

В практических измерениях часто обнаруживается, что именно первая сборка занимает в 5–20 раз больше времени, особенно при большом количестве зависимостей.

Для логирования изменений в watch-режиме можно использовать хук watchChange в пользовательской интеграции через API, либо оборачивать запуск процесса:

let first = true;

watch.on('event', event => {
  if (event.code === 'BUNDLE_START') {
    start = performance.now();
  }

  if (event.code === 'BUNDLE_END') {
    const end = performance.now();

    if (first) {
      console.log('Initial build:', end - start);
      first = false;
    } else {
      console.log('Rebuild:', end - start);
    }
  }
});

Влияние кэша на время сборки

Кэш Rollup является ключевым фактором ускорения. При использовании программного API он передаётся явно:

let cache;

const bundle = await rollup({
  input: 'src/index.js',
  cache
});

cache = bundle.cache;

Измерение времени без кэша и с кэшем даёт объективную картину эффективности оптимизации. Разница особенно заметна при:

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

Стоимость отдельных стадий сборки

Rollup сборка концептуально делится на несколько этапов, и каждый имеет разную “цену”:

1. Построение графа модулей Зависит от количества import и сложности резолвинга. Плагины resolveId могут сильно увеличивать время.

2. Загрузка модулей Включает файловую систему и виртуальные модули.

3. Трансформация кода Наиболее дорогой этап при использовании Babel, TypeScript или AST-плагинов.

4. Tree-shaking Зависит от структуры модулей и наличия side effects.

5. Генерация бандлов Увеличивается при большом числе чанков.

6. Запись на диск Может стать узким местом при больших sourcemap.

Влияние sourcemap на производительность

Генерация sourcemap часто недооценивается. При sourcemap: true Rollup дополнительно строит соответствие между исходным и итоговым кодом, что может заметно увеличивать время генерации.

Особенно критично это при:

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

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

Профилирование через Node.js Inspector

Для глубокой диагностики используется встроенный профилировщик Node.js:

node --inspect node_modules/rollup/dist/bin/rollup -c

Далее через Chrome DevTools можно получить flame graph выполнения, где видно:

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

Этот метод особенно полезен при нестабильных или “скачущих” временах сборки.

Сравнительное измерение конфигураций

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

  • без минификации vs с terser
  • без sourcemap vs с sourcemap
  • разные плагины трансформации
  • разные стратегии code splitting

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

Логирование пользовательских метрик

В реальных проектах часто создаётся собственная система метрик поверх Rollup API. Она может включать:

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

Пример агрегированного подхода:

const start = performance.now();

const bundle = await rollup(inputOptions);

const generateStart = performance.now();
const output = await bundle.generate(outputOptions);

const end = performance.now();

return {
  modules: bundle.cache?.modules?.length,
  generateTime: end - generateStart,
  totalTime: end - start
};

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

Выводы из измерений как инженерная практика

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