Опция resolveExtensions

В системе модульного разрешения JavaScript и TypeScript важную роль играет порядок, в котором сборщик пытается сопоставить импортируемые пути с реальными файлами. В esbuild эта логика управляется параметром resolveExtensions, который определяет, какие расширения файлов будут учитываться при разрешении импортов и в каком порядке.


Общая роль resolveExtensions

При встрече импорта вида:

import utils from "./utils"

esbuild должен определить, какой именно файл соответствует этому пути. Возможные варианты:

  • ./utils.js
  • ./utils.ts
  • ./utils.jsx
  • ./utils.tsx
  • ./utils.json

Опция resolveExtensions задаёт список расширений, которые будут проверяться при отсутствии явного расширения в импорте.


Сигнатура и базовое поведение

По умолчанию esbuild использует следующий набор расширений:

resolveExtensions: [".ts", ".tsx", ".js", ".jsx", ".json"]

Алгоритм разрешения работает последовательно:

  1. Берётся импорт без расширения.
  2. Последовательно добавляются расширения из resolveExtensions.
  3. Проверяется существование файла.
  4. Возвращается первый найденный результат.

Порядок расширений и приоритет

Порядок элементов в массиве критически важен. esbuild не анализирует «тип проекта» или предпочтения пользователя — он строго идёт сверху вниз.

Пример конфигурации:

{
  resolveExtensions: [".ts", ".js"]
}

При наличии двух файлов:

utils.ts
utils.js

импорт:

import utils from "./utils"

разрешится в utils.ts, потому что .ts имеет приоритет.

Если изменить порядок:

resolveExtensions: [".js", ".ts"]

результатом станет utils.js.


Влияние на TypeScript-проекты

В TypeScript-экосистеме порядок расширений часто используется для контроля поведения при смешанных кодовых базах.

Типичный сценарий:

resolveExtensions: [".ts", ".tsx", ".js"]

Это позволяет:

  • отдавать приоритет TypeScript-реализациям
  • постепенно мигрировать проект с JS на TS
  • сохранять обратную совместимость

Взаимодействие с platform и loader

Опция resolveExtensions тесно связана с двумя другими механизмами esbuild:

1. loader

Loader определяет, как интерпретировать конкретные расширения:

loader: {
  ".ts": "ts",
  ".js": "js",
  ".json": "json"
}

resolveExtensions лишь выбирает файл, а loader определяет, как его обработать.

2. platform

Платформа влияет на допустимые расширения и поведение резолва:

  • browser — ориентирован на фронтенд, часто включает .jsx
  • node — учитывает Node.js-экосистему

Но именно resolveExtensions задаёт приоритет поиска.


Разрешение без расширения

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

import config from "./config"

esbuild пробует:

./config.ts
./config.tsx
./config.js
./config.jsx
./config.json

в зависимости от конфигурации.


Разрешение с явным расширением

Если импорт содержит расширение, resolveExtensions полностью игнорируется:

import data from "./data.json"

В этом случае:

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

Влияние на производительность

Хотя resolveExtensions кажется простой опцией, её неправильная настройка может влиять на скорость сборки.

Факторы влияния:

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

Пример неоптимальной конфигурации:

resolveExtensions: [
  ".jsx",
  ".tsx",
  ".ts",
  ".js",
  ".mjs",
  ".cjs",
  ".json"
]

Здесь поиск может становиться более дорогим в больших проектах, особенно при глубокой файловой структуре.


Работа с монорепозиториями

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

Пример:

resolveExtensions: [".ts", ".tsx", ".js", ".jsx"]

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

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

Конфликты расширений

При наличии файлов с одинаковыми именами, но разными расширениями, resolveExtensions определяет исход конфликта.

Структура:

service.ts
service.js
service.json

Импорт:

import service from "./service"

Результат зависит от порядка:

  • .ts → TypeScript версия
  • .js → JavaScript версия
  • .json → конфигурационный объект

Совместимость с ESM и CommonJS

В зависимости от типа модулей resolveExtensions участвует в разных сценариях:

ESM:

  • предпочтение современным расширениям .js, .mjs, .ts

CommonJS:

  • чаще используется .js, .cjs

esbuild не различает семантику модулей при resolveExtensions, но порядок влияет на итоговую интерпретацию.


Особенности поведения при отсутствующих файлах

Если ни один вариант расширения не найден, esbuild:

  • возвращает ошибку resolution failure
  • прерывает сборку (если не включены специальные плагины)

Пример:

Could not resolve "./utils"

Использование в реальных конфигурациях

Типичная конфигурация для универсального проекта:

require("esbuild").build({
  entryPoints: ["src/index.ts"],
  bundle: true,
  outdir: "dist",
  resolveExtensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
})

Для чистого Node.js проекта:

resolveExtensions: [".js", ".mjs", ".cjs", ".json"]

Для фронтенд React-проекта:

resolveExtensions: [".tsx", ".ts", ".jsx", ".js"]

Взаимодействие с alias и plugins

Хотя resolveExtensions отвечает за расширения, он часто используется вместе с:

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

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


Поведение при глубокой вложенности

При глубокой структуре каталогов:

src/modules/user/profile/service

resolveExtensions применяется одинаково на каждом уровне резолва, без изменения логики.

Импорт:

import service from "./modules/user/profile/service"

поиск расширений происходит в каждом каталоге независимо.


Ограничения опции

  • не поддерживает регулярные выражения
  • не учитывает тип файлов по содержимому
  • не различает entrypoint и dependency
  • не влияет на runtime поведение кода

Практические паттерны настройки

Миграция JS → TS

resolveExtensions: [".ts", ".js"]

Максимальная совместимость

resolveExtensions: [".ts", ".tsx", ".js", ".jsx", ".json"]

Node.js-ориентированная сборка

resolveExtensions: [".js", ".mjs", ".cjs"]

Минималистичная конфигурация

resolveExtensions: [".js"]

Поведение при использовании index файлов

При импортах директорий:

import utils from "./utils"

и структуре:

utils/index.ts
utils/index.js

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


Резюме механики работы

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