Синхронные, асинхронные, параллельные и последовательные хуки

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

Синхронные хуки выполняются немедленно и возвращают результат без ожидания Promise. Их поведение максимально предсказуемо: следующий шаг конвейера не начинается, пока текущий хук не завершён и не вернул значение.

Ключевая особенность синхронных хуков заключается в том, что они не допускают асинхронных операций. Любая попытка вернуть Promise будет интерпретироваться Rollup как ошибка или приведёт к некорректному поведению в зависимости от контекста исполнения.

Типичные синхронные хуки используются там, где не требуется I/O или задержки:

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

Пример поведения:

  • вызывается хук
  • выполняется код
  • возвращается значение
  • управление передаётся следующему этапу

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

Асинхронные хуки

Асинхронные хуки возвращают Promise и позволяют выполнять операции, зависящие от внешних ресурсов: чтение файлов, сетевые запросы, обращение к кэшу или базе данных.

Асинхронная модель в Rollup полностью поддерживается через async/await. При этом движок сборки ожидает завершения Promise перед переходом к следующему этапу.

Поведение асинхронного хука:

  • вызов хука
  • получение Promise
  • ожидание завершения Promise
  • продолжение pipeline

Асинхронность особенно важна в хуках:

  • load — загрузка содержимого модулей из файловой системы
  • transform — асинхронная трансформация кода (например, Babel, TypeScript)
  • resolveId — возможное разрешение путей через внешние источники

Асинхронные хуки добавляют гибкость, но увеличивают стоимость сборки, особенно при большом количестве модулей, где каждый Promise добавляет overhead планирования микротасков.

Параллельное выполнение хуков

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

Параллельность используется там, где результат каждого плагина не влияет напрямую на результат других плагинов.

К таким хукам относятся:

  • buildStart
  • buildEnd
  • closeBundle
  • некоторые этапы генерации, не изменяющие поток модулей

Модель выполнения:

  • Rollup вызывает хук у всех плагинов
  • каждый хук запускается независимо
  • результаты агрегируются после завершения всех Promise

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

Параллельные хуки полезны для:

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

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

Последовательные хуки

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

Наиболее характерный пример — хук transform.

Модель выполнения:

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

Если один из плагинов возвращает null, модуль может быть пропущен из дальнейшей обработки.

Последовательность особенно важна для:

  • transform — цепочка модификаций кода
  • load — последовательное предоставление содержимого
  • частично resolveId, где первый успешный ответ прерывает цепочку

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

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

Комбинированные модели выполнения

В реальной системе Rollup большинство хуков не относятся строго к одной категории. Один и тот же плагин может содержать:

  • синхронный resolveId
  • асинхронный load
  • последовательный transform
  • параллельный buildStart

Комбинация моделей создаёт многоуровневую систему исполнения:

  1. Сначала параллельные хуки инициируют фазы сборки
  2. Затем последовательные цепочки формируют граф модулей
  3. Асинхронные операции заполняют данные из внешних источников
  4. Синхронные хуки выполняют быстрые локальные вычисления

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

Влияние модели выполнения на поведение сборки

Разделение на синхронные, асинхронные, параллельные и последовательные хуки напрямую влияет на:

1. Производительность

Параллельные хуки уменьшают общее время инициализации сборки за счёт одновременного выполнения. Асинхронные хуки могут как ускорять процесс (при I/O), так и замедлять его (из-за ожидания Promise).

2. Детерминированность

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

3. Контроль потока данных

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

4. Расширяемость плагинов

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

Особенности взаимодействия хуков в одном плагине

Один плагин может одновременно участвовать в разных типах исполнения. Например:

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

Это создаёт многоуровневую модель, где один и тот же плагин участвует в разных контекстах исполнения.

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

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

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

  • стоимость асинхронных операций в горячих путях (transform)
  • недопустимость побочных эффектов в параллельных хуках
  • важность порядка подключения плагинов для последовательных цепочек
  • ограничение на синхронные операции в I/O-зависимых этапах

Эти правила формируют основу предсказуемой работы сборщика и позволяют комбинировать плагины разных авторов без конфликтов в логике исполнения.