Фильтры: filter и namespace

В системе плагинов esbuild обработка модулей строится вокруг двух ключевых механизмов: регулярного фильтра filter и пространств имён namespace. Эти параметры используются в хуках onResolve и onLoad и позволяют точно управлять тем, какие модули перехватываются, как они интерпретируются и на каком этапе сборки обрабатываются.


filter в onResolve и onLoad

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

Роль filter в onResolve

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

Пример базовой структуры:

const plugin = {
  name: 'example',
  setup(build) {
    build.onResolve({ filter: /\.txt$/ }, (args) => {
      return {
        path: args.path,
        namespace: 'text-ns'
      };
    });
  }
};

В данном случае:

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

Особенности работы filter в onResolve

  1. Сопоставление выполняется по строке импорта, а не по реальному пути на диске.
  2. Регулярное выражение применяется до нормализации путей.
  3. Несовпадающие модули полностью исключаются из данного обработчика.
  4. Несколько onResolve могут работать параллельно, если их фильтры пересекаются.

filter в onLoad

В onLoad фильтрация применяется уже после резолва пути и выбора пространства имён. Здесь filter чаще используется для ограничения обработки по расширению или шаблону пути.

build.onLoad({ filter: /\.txt$/, namespace: 'text-ns' }, (args) => {
  return {
    contents: 'hello world',
    loader: 'text'
  };
});

В этом случае обработка происходит только если:

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

Пространства имён (namespace)

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


Стандартное поведение namespace

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

file

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


Использование кастомных namespace

Плагины часто создают собственные пространства имён для разделения логики обработки:

build.onResolve({ filter: /^virtual:/ }, (args) => {
  return {
    path: args.path,
    namespace: 'virtual-ns'
  };
});

Здесь:

  • импорт вида virtual:module перенаправляется в пространство virtual-ns
  • дальнейшая обработка происходит отдельно от файловой системы

Связка namespace между onResolve и onLoad

Основной механизм работы строится на передаче namespace между этапами:

  1. onResolve определяет, в каком пространстве будет находиться модуль
  2. onLoad перехватывает модуль уже внутри этого пространства
build.onResolve({ filter: /^virtual:/ }, (args) => {
  return {
    path: args.path,
    namespace: 'virtual-ns'
  };
});

build.onLoad({ filter: /.*/, namespace: 'virtual-ns' }, (args) => {
  return {
    contents: 'export const value = 42;',
    loader: 'js'
  };
});

Такой подход позволяет:

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

Взаимодействие filter и namespace

filter и namespace выполняют разные функции и используются совместно:

  • filter ограничивает совпадение по строковому шаблону
  • namespace ограничивает область действия по контексту модуля

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

build.onLoad(
  { filter: /\.txt$/, namespace: 'text-ns' },
  (args) => {
    return {
      contents: 'processed text',
      loader: 'text'
    };
  }
);

Логика обработки:

  1. Проверка namespace
  2. Проверка filter
  3. Выполнение обработчика

Практические сценарии применения

Виртуальные модули

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

  • конфигурации, генерируемые на лету
  • встроенные шаблоны
  • динамически создаваемые API-клиенты

Перехват специфических ресурсов

filter позволяет ограничивать обработку по расширениям или паттернам:

  • изображения (\.png$, \.svg$)
  • стили (\.css$)
  • нестандартные форматы (\.graphql$, \.md$)

Разделение источников данных

Комбинация filter и namespace позволяет строить многоуровневую систему загрузчиков:

  • file — стандартные файлы
  • http-ns — удалённые ресурсы
  • virtual-ns — сгенерированный код
  • asset-ns — бинарные ресурсы

Порядок обработки и приоритет

esbuild обрабатывает плагины последовательно, но filter влияет на ранний этап маршрутизации.

При совпадении нескольких плагинов:

  1. Сначала применяются onResolve
  2. Затем выбирается первый подходящий результат
  3. Далее вызываются onLoad с уже установленным namespace

Конфликтов между filter и namespace не происходит напрямую, но некорректная настройка может привести к пропуску обработчиков.


Типичные ошибки при использовании

Слишком широкие регулярные выражения

filter: /.*/

Приводит к перехвату всех модулей и снижению предсказуемости сборки.


Несогласованность namespace

onResolve -> namespace: 'custom'
onLoad    -> namespace: 'custom-ns'

Результат: onLoad не срабатывает.


Отсутствие фильтра в onLoad

Без filter обработчик может срабатывать на неожиданные пути внутри одного namespace, что усложняет отладку.


Роль в архитектуре плагинов esbuild

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

  • перехвачен на этапе резолва
  • перенаправлен в альтернативное пространство
  • обработан специализированным загрузчиком

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