Возвращаемые значения хуков и их приоритеты

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

Общая модель возврата значений

Хуки Esbuild работают по принципу цепочки обработчиков. Для каждого события (разрешение пути, загрузка модуля, трансформация кода) может быть зарегистрировано несколько функций. Каждая из них может:

  • вернуть объект результата, влияющий на дальнейшую сборку
  • вернуть undefined, передавая управление следующему обработчику
  • вернуть частичный результат, дополняемый другими хуками (в зависимости от типа хука)

Ключевое правило: первый хук, вернувший значимый результат, может прервать дальнейшую цепочку обработки.


Хук onResolve: приоритеты и возврат значений

Структура возвращаемого объекта

Хук onResolve используется для определения того, как Esbuild должен интерпретировать импортируемый путь:

{
  path: string,
  namespace?: string,
  external?: boolean,
  sideEffects?: boolean,
  pluginData?: any,
  watchFiles?: string[]
}

Поведение при возврате undefined

Если хук возвращает undefined, Esbuild продолжает искать следующий подходящий onResolve, зарегистрированный для данного фильтра. Это создает естественный механизм приоритетов: порядок регистрации имеет значение.


Прерывание цепочки

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

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

  1. Хук A возвращает undefined
  2. Хук B возвращает { path: '...' }
  3. Хук C не вызывается (если B полностью удовлетворил запрос)

Приоритет через фильтры

Приоритет onResolve определяется не только порядком регистрации, но и точностью фильтра:

  • более специфичный filter имеет приоритет над общим
  • более специфичный namespace перекрывает общий namespace
  • совпадение по suffix и prefix повышает приоритет

Таким образом, фактическая система приоритетов является комбинацией:

специфичность > порядок регистрации > namespace


Влияние external в возврате

Если хук возвращает:

{ external: true }

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


Хук onLoad: конкуренция источников загрузки

Структура результата

{
  contents: string | Uint8Array,
  loader?: string,
  resolveDir?: string,
  pluginData?: any,
  watchFiles?: string[]
}

Механика приоритетов

onLoad вызывается после успешного onResolve. Здесь также действует цепочка:

  • первый хук, вернувший contents, завершает загрузку
  • остальные хуки игнорируются
  • если возвращено undefined, выполняется следующий onLoad

Приоритет по namespace

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

Это создаёт строгую изоляцию:

  • namespace: "file" — стандартные файлы
  • namespace: "http" — удалённые ресурсы
  • пользовательские namespace — полностью управляемая среда

Хуки разных namespace не конкурируют между собой, что формирует отдельные приоритетные контуры.


Поведение при нескольких loader-ах

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

  1. namespace совпадает
  2. filter совпадает наиболее точно
  3. порядок регистрации

Первый хук, вернувший contents, блокирует остальные.


Приоритеты трансформации: onLoad и последующая стадия

После onLoad начинается этап трансформации (внутренний или через onTransform, если используется косвенная логика плагина).

Хотя Esbuild не предоставляет классический onTransform как основной API, плагины часто реализуют трансформацию через:

  • изменение contents в onLoad
  • использование pluginData для передачи состояния между хуками

Цепочка модификаций содержимого

Если несколько хуков участвуют в изменении содержимого, возникает правило:

  • последний вернувший contents определяет итоговое значение
  • промежуточные данные могут быть потеряны, если не сохранены в pluginData

Это создаёт скрытый приоритет: поздние хуки имеют доминирующее влияние на содержимое.


pluginData как механизм приоритетного состояния

pluginData не влияет напрямую на приоритет выполнения, но используется для его эмуляции.

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

  • передаётся между onResolve и onLoad
  • доступен только внутри одного плагина, если не передан явно
  • может использоваться для маркировки “владельца” модуля

Типичный паттерн:

  • первый хук помечает модуль через pluginData
  • последующие хуки проверяют наличие метки
  • при наличии метки обработка пропускается

Это создаёт логический приоритет, не зависящий от порядка регистрации.


Конфликтующие хуки и стратегия разрешения

Когда несколько плагинов обрабатывают один и тот же модуль, возможны три сценария:

1. Победа по порядку регистрации

Самый простой случай:

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

2. Победа по специфичности фильтра

Более точный filter перекрывает общий:

  • /\.js$/ проигрывает /src\/.*\.js$/
  • универсальные хуки работают как fallback

3. Победа по namespace

Если namespace различается, конкуренции нет. Это жёсткое разделение контекстов.


Возвращаемые значения как механизм управления сборкой

В onResolve:

  • path — изменяет маршрут модуля
  • external — исключает модуль
  • namespace — перенаправляет в другой контекст
  • watchFiles — добавляет зависимости для наблюдения

Каждое из этих полей может изменить приоритет всей цепочки сборки.


В onLoad:

  • contents — финальный источник данных модуля
  • loader — определяет тип интерпретации (js, json, text и др.)
  • resolveDir — влияет на дальнейшие import внутри файла

Неявные приоритеты внутри пайплайна

Помимо явных правил существует ряд неочевидных приоритетов:

1. Built-in обработчики Esbuild

Встроенные загрузчики имеют самый низкий приоритет, но являются fallback-слоем.

2. Плагины с ранним регистрационным порядком

Ранние плагины часто перехватывают поток до стандартной обработки.

3. Специфичные namespace-хуки

Изолированные контексты практически не конфликтуют, но имеют абсолютный приоритет внутри своего пространства.


Поведение при множественных возвратах

Если хук возвращает разные поля, важно понимать:

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

Это означает, что:

каждый хук должен возвращать полностью консистентный результат


Приоритет ошибок и исключений

Если хук выбрасывает исключение:

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

В отличие от return undefined, исключение не является “мягким” отказом и имеет более высокий приоритет влияния на пайплайн.


Итоговая модель приоритетов

Система приоритетов Esbuild в хуках формируется из нескольких слоёв:

  1. Namespace изоляция — высший уровень разделения
  2. Специфичность фильтра — точность совпадения
  3. Порядок регистрации плагинов — линейный приоритет
  4. Факт возврата значения — прекращение цепочки
  5. Тип возвращаемого значения — влияние на дальнейший пайплайн
  6. pluginData-логика — пользовательская система приоритетов

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