rollup.watch(): объект watcher и его события

rollup.watch() создаёт процесс наблюдения за изменениями входных файлов и запускает повторную сборку при каждом изменении зависимостей. В отличие от одноразового вызова rollup.rollup(), который формирует бандл один раз, режим наблюдения работает в долгоживущем процессе и управляется объектом watcher, который возвращается из функции.

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


Функция rollup.watch() принимает массив конфигураций или одну конфигурацию, аналогичную rollup.rollup(), но с дополнительной логикой наблюдения за файловой системой.

import { watch } from 'rollup';

const watcher = watch({
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'esm'
  }
});

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


Объект watcher и его природа

Возвращаемый объект watcher является управляющим интерфейсом над процессом наблюдения. Он не является простым результатом сборки, а представляет активный процесс, который может:

  • отслеживать изменения файлов
  • запускать повторные сборки
  • эмитить события жизненного цикла
  • быть остановленным вручную
const watcher = watch(config);

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


Событие event: базовая модель взаимодействия

Watcher реализует событийную модель. Основное событие, через которое происходит взаимодействие, — event.

watcher.on('event', (event) => {
  console.log(event.code);
});

Каждое событие имеет поле code, которое определяет тип состояния сборки.


Событие START

code: START

Вызывается один раз при запуске watcher.

{
  code: 'START'
}

Смысл события заключается в инициализации процесса наблюдения. На этом этапе ещё нет сборки, но файловая система уже отслеживается.


Событие BUNDLE_START

code: BUNDLE_START

Срабатывает перед началом каждой новой сборки.

{
  code: 'BUNDLE_START',
  input: 'src/index.js',
  output: [{ file: 'dist/bundle.js' }]
}

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

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

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

Событие BUNDLE_END

code: BUNDLE_END

Срабатывает после успешного завершения сборки.

{
  code: 'BUNDLE_END',
  duration: 120,
  result: bundle
}

Поле duration содержит время сборки в миллисекундах. Поле result содержит объект bundle, аналогичный результату rollup.rollup().

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

result можно использовать для:

  • генерации выходных файлов через bundle.write()
  • получения метаданных через bundle.generate()
  • анализа графа модулей
watcher.on('event', async (event) => {
  if (event.code === 'BUNDLE_END') {
    await event.result.write({
      file: 'dist/bundle.js',
      format: 'esm'
    });
  }
});

Событие END

code: END

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

{
  code: 'END'
}

Это событие означает завершение конкретного цикла сборки, но не остановку watcher.


Событие ERROR

code: ERROR

Срабатывает при ошибках на любом этапе сборки или наблюдения.

{
  code: 'ERROR',
  error: Error,
  plugin: 'node-resolve'
}

Поле error содержит объект ошибки, включая стек вызовов и описание. Поле plugin указывает источник ошибки, если она произошла внутри плагина.

Поведение watcher при ошибках

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

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

close()

Метод close() завершает процесс наблюдения.

await watcher.close();

После вызова:

  • файловые наблюдатели отключаются
  • события больше не эмитятся
  • процесс сборки прекращается

Используется при завершении dev-сервера или CI-окружения.


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

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

При изменении файла происходит:

  1. фиксация изменения файловой системой
  2. пересборка зависимого подграфа
  3. запуск нового цикла BUNDLE_START
  4. генерация нового bundle
  5. событие BUNDLE_END

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


Псевдопоследовательность событий

Типичный цикл watcher выглядит следующим образом:

START
BUNDLE_START
BUNDLE_END
END
BUNDLE_START
BUNDLE_END
END
...

Цикл повторяется до вызова close() или завершения процесса Node.js.


Множественные конфигурации

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

const watcher = watch([
  {
    input: 'src/index.js',
    output: { file: 'dist/a.js', format: 'esm' }
  },
  {
    input: 'src/admin.js',
    output: { file: 'dist/b.js', format: 'esm' }
  }
]);

Каждая конфигурация генерирует собственные события BUNDLE_START и BUNDLE_END.


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

Плагины могут реагировать на изменения в watch-режиме через hook watchChange.

export default function myPlugin() {
  return {
    watchChange(id) {
      console.log('Изменён файл:', id);
    }
  };
}

Это позволяет интегрировать кастомную логику кэширования, логирования или условной пересборки.


Особенности стабильности и поведения

Watcher не гарантирует строгую синхронность событий в высоконагруженных проектах. Возможны следующие особенности:

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

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

События watcher часто используются для построения dev-инструментов:

  • интеграция с dev-server
  • отображение прогресса сборки
  • горячая перезагрузка
  • логирование производительности

Пример обработки всех ключевых состояний:

watcher.on('event', (event) => {
  switch (event.code) {
    case 'START':
      break;
    case 'BUNDLE_START':
      break;
    case 'BUNDLE_END':
      event.result.close?.();
      break;
    case 'ERROR':
      break;
    case 'END':
      break;
  }
});

Синхронизация с файловой системой

Watcher опирается на системные механизмы наблюдения (chokidar-подобный слой в зависимости от платформы). Это означает:

  • изменения отслеживаются на уровне ОС
  • поддерживаются рекурсивные директории
  • игнорируются изменения вне графа зависимостей (в большинстве случаев)

Жизненный цикл процесса watcher

Полный жизненный цикл можно представить как последовательность состояний:

  • инициализация watcher
  • построение графа зависимостей
  • первый цикл сборки
  • ожидание изменений файлов
  • инкрементальные пересборки
  • завершение через close()

Каждое состояние сопровождается событиями, формирующими управляемую реактивную систему сборки.