Хуки watchChange и closeWatcher

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

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


Архитектура watch-режима

Режим rollup --watch или программный API rollup.watch() создаёт специализированный watcher-инстанс, который:

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

Внутри этого процесса существует отдельный слой событий, отличающийся от стандартных плагин-хуков (buildStart, resolveId, load, transform и др.). Этот слой обслуживает именно поведение наблюдателя.


Хук watchChange

Назначение

watchChange вызывается при любом изменении файла, который отслеживается watcher-ом. Это может быть:

  • изменение содержимого исходного файла;
  • добавление нового файла в граф;
  • удаление файла;
  • изменения, обнаруженные через файловую систему (fs events);
  • триггеры, вызванные плагинами, эмулирующими изменение.

Хук предназначен для реакции на события уровня файловой системы до начала новой сборки.


Сигнатура и контекст

Хук имеет следующую форму:

watchChange(id, change)

Где:

  • id — абсолютный путь к изменённому модулю;
  • change — объект, описывающий тип изменения.

Пример структуры change:

{
  event: 'create' | 'update' | 'delete'
}

Порядок вызова

watchChange вызывается:

  1. После получения события от файловой системы;
  2. До запуска нового цикла сборки;
  3. До инвалидирования модулей в графе зависимостей.

Это делает хук ранней точкой вмешательства в реакцию системы на изменения.


Поведение в плагинах

Плагин может определить watchChange следующим образом:

export default function myPlugin() {
  return {
    watchChange(id, { event }) {
      if (event === 'delete') {
        // логика реакции на удаление файла
      }
    }
  };
}

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


Ограничения

  • Хук не влияет на граф модулей напрямую;
  • Возвращаемые значения игнорируются;
  • Асинхронность не поддерживается как управляющий механизм;
  • Не предназначен для трансформации данных.

Типичные сценарии использования

Логирование изменений

Используется для диагностики процессов сборки:

watchChange(id, { event }) {
  console.log(`[watch] ${event}: ${id}`);
}

Инвалидация внешних кэшей

При наличии внешних систем кэширования можно синхронизировать их с изменениями:

watchChange(id, { event }) {
  if (event === 'update') {
    externalCache.invalidate(id);
  }
}

Интеграция с dev-серверами

При разработке dev-серверов на базе Rollup можно использовать этот хук для уведомления UI:

watchChange(id, { event }) {
  websocket.send(JSON.stringify({ type: 'file-change', id, event }));
}

Хук closeWatcher

Назначение

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

Основные задачи:

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

Сигнатура

closeWatcher()

Хук не принимает аргументов и не возвращает значимого результата.


Момент вызова

closeWatcher вызывается:

  • при остановке процесса rollup --watch;
  • при вызове watcher.close() из API;
  • при завершении процесса Node.js, если watcher не был закрыт явно.

Он является финальной точкой жизненного цикла watcher-а.


Поведение в плагинах

Плагин может зарегистрировать обработчик:

export default function myPlugin() {
  return {
    closeWatcher() {
      cleanupTemporaryFiles();
      closeDatabaseConnections();
    }
  };
}

Важные особенности

Гарантированность вызова

Хук вызывается при корректном завершении watcher-а, но не гарантируется при аварийном завершении процесса (например, SIGKILL).

Синхронность

В большинстве реализаций обработка выполняется синхронно или в режиме, не блокирующем критический shutdown-поток.

Отсутствие повторных вызовов

Хук вызывается единожды для каждого watcher-инстанса.


Взаимодействие watchChange и closeWatcher

Оба хука относятся к жизненному циклу наблюдения, но находятся в разных фазах:

  • watchChange — активная фаза наблюдения;
  • closeWatcher — фаза завершения и очистки.

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

  1. Инициализация watcher-а;
  2. Обнаружение изменений файлов;
  3. Вызовы watchChange;
  4. Пересборка графа;
  5. Повтор циклов 2–4;
  6. Завершение watcher-а;
  7. Вызов closeWatcher.

Внутренние аспекты реализации

Система событий watcher-а

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

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

watchChange вызывается после нормализации этих событий.


Инвалидация модулей

После watchChange Rollup определяет, какие модули:

  • должны быть пересобраны;
  • должны быть удалены из кэша;
  • требуют повторного анализа зависимостей.

Таким образом, watchChange находится до фазы graph invalidation.


Закрытие watcher-а

При вызове closeWatcher происходит:

  • остановка file watchers;
  • очистка внутренних очередей задач;
  • завершение debounce-таймеров;
  • вызов closeWatcher во всех плагинах.

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

Использование watchChange и closeWatcher позволяет выстраивать плагины с учётом жизненного цикла разработки:

  • синхронизация внешних сервисов;
  • управление временными файлами;
  • интеграция с IDE и dev tools;
  • контроль утечек ресурсов при длительном watch-режиме.

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