Разрешение путей: алгоритм и приоритеты

Разрешение модулей в esbuild начинается с анализа значения import или require и определения типа пути: абсолютный, относительный или пакетный. От этого зависит дальнейшая стратегия поиска ресурса и набор применяемых правил.

Первичный этап — определение формы спецификатора:

  • Относительные пути (./, ../) — интерпретируются относительно файла, в котором происходит импорт.
  • Абсолютные пути (/) — обрабатываются через корневую файловую систему или виртуальные корни, заданные плагинами.
  • Пакетные идентификаторы (react, lodash/debounce) — проходят через систему поиска node_modules.

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

Базовый алгоритм поиска файла

Для относительных и абсолютных путей применяется последовательная стратегия проверки:

  1. Проверка точного совпадения файла.

  2. Попытка добавления расширений в порядке приоритета:

    • .tsx
    • .ts
    • .jsx
    • .js
    • .json
    • .css (при включённых соответствующих loader-ах)
  3. Проверка директорий:

    • поиск index файлов внутри папки (index.js, index.ts и т.д.)

Этот этап выполняется до обращения к системе пакетов, если путь не является package import.

Приоритет расширений

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

Ключевой момент: esbuild не использует глобальную систему расширений Node.js, а опирается на конфигурацию сборки (loader, resolveExtensions через плагины).

Разрешение пакетов (node_modules)

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

  1. Текущая директория модуля.
  2. Родительские директории до корня файловой системы.
  3. В каждой директории проверяется node_modules/<package>.

Дальнейшее разрешение зависит от содержимого package.json.

Приоритет полей package.json

esbuild анализирует метаданные пакета для определения точки входа. Приоритет зависит от режима сборки (platform, format, conditions):

Основные поля:

  • exports — имеет наивысший приоритет при наличии.
  • module — используется для ESM-сборок.
  • main — fallback для CommonJS.
  • browser — переопределяет поведение в браузерном режиме.

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

Алгоритм работы с exports

Поле exports представляет собой дерево условий:

  1. Проверка точного совпадения подпути (".", "./subpath").

  2. Последовательная проверка условий:

    • import
    • require
    • default
    • пользовательские условия (browser, node, production)

Выбор происходит по первому совпавшему условию, соответствующему текущему контексту сборки.

Если условие ведёт к строке — выполняется повторное разрешение пути.

Роль условий платформы

esbuild учитывает контекст сборки:

  • platform: browser влияет на выбор browser-полей.
  • platform: node отключает некоторые браузерные подмены.
  • format: esm/cjs/iife влияет на интерпретацию module и main.

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

Обработка директорий пакетов

Если импорт указывает на пакет без конкретного файла:

  • Проверяется exports["."]
  • Затем main или module
  • Затем index.js как fallback

Директория считается валидной точкой входа только при наличии одного из этих элементов.

Разрешение вложенных путей пакета

Импорты вида package/subpath обрабатываются через:

  1. Поиск совпадения в exports.
  2. Проверка наличия физического файла в node_modules/package/subpath.
  3. Применение fallback-правил Node resolution.

Если exports присутствует и не содержит соответствующего подпути, прямой доступ блокируется.

Плагины и перехват разрешения

Ключевое отличие esbuild от классического Node resolution — наличие системы плагинов.

Плагины могут:

  • перехватывать любой импорт (onResolve)
  • подменять путь
  • изменять приоритет поиска
  • эмулировать виртуальные модули

При этом порядок обработки следующий:

  1. Все зарегистрированные плагины onResolve (в порядке регистрации).
  2. Встроенный resolver esbuild.
  3. Файловая система.

Первый успешный onResolve может полностью остановить дальнейшее разрешение.

Кэширование результатов

esbuild использует агрессивное кэширование:

  • результат разрешения пути кешируется по ключу:

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

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

Обработка циклов и неоднозначностей

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

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

  1. Явное совпадение расширения.
  2. Приоритет loader-ов.
  3. Порядок файловой системы (не гарантируется, но стабилизируется кэшем).

Особенности TypeScript и JSX

Файлы .ts, .tsx и .jsx участвуют в разрешении наравне с .js, но:

  • TypeScript path mapping не обрабатывается ядром esbuild
  • требуется плагин для tsconfig.json
  • JSX-расширения учитываются только при активном JSX loader

Это влияет на этап выбора расширения, но не на общий алгоритм поиска.

Итоговая структура приоритетов

Обобщённый порядок разрешения:

  1. onResolve плагины
  2. Проверка относительных/абсолютных путей
  3. Расширения файлов
  4. Директории и index
  5. Поиск в node_modules
  6. package.json.exports
  7. module / main / browser
  8. fallback к индексным файлам пакета

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