Опция alias: переопределение путей к модулям

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

Механизм разрешения модулей в esbuild

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

Ключевой хук:

  • onResolve — перехватывает пути импортов до их обработки
  • filter — задаёт правила перехвата (регулярные выражения)
  • namespace — разделяет области виртуальных модулей

Именно через onResolve реализуется поведение, аналогичное alias.

Базовая реализация alias через плагин

Переопределение путей строится на сопоставлении префикса импорта с целевым абсолютным или относительным путём.

import path from "path";

const aliasPlugin = (aliases) => ({
  name: "alias-plugin",
  setup(build) {
    for (const [find, replacement] of Object.entries(aliases)) {
      const filter = new RegExp(`^${find}(/.*)?$`);

      build.onResolve({ filter }, (args) => {
        const replacedPath = args.path.replace(find, replacement);

        return {
          path: path.resolve(replacedPath),
        };
      });
    }
  },
});

Использование:

import esbuild from "esbuild";

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  outfile: "dist/app.js",
  plugins: [
    aliasPlugin({
      "@components": "./src/components",
      "@utils": "./src/utils",
    }),
  ],
});

Логика работы подмены путей

При импорте:

import Button from "@components/Button";

плагин выполняет следующие шаги:

  1. Срабатывает onResolve при совпадении с регулярным выражением
  2. Извлекается оригинальный путь импорта
  3. Производится замена префикса (@components)
  4. Формируется абсолютный путь к файлу
  5. esbuild продолжает стандартный процесс загрузки модуля

Обработка вложенных путей

Alias должен корректно обрабатывать поддиректории:

@components/ui/Button
@components/layout/Header

Корректная реализация сохраняет остаточную часть пути:

const filter = new RegExp(`^${find}(\\/.*)?$`);

build.onResolve({ filter }, (args) => {
  const rest = args.path.slice(find.length);
  return {
    path: path.join(replacement, rest),
  };
});

Абсолютные и относительные пути

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

Абсолютный вариант:

const aliases = {
  "@root": path.resolve("./src"),
};

Относительный вариант:

const aliases = {
  "@api": "./src/api",
};

При использовании относительных путей важно нормализовать путь через path.resolve, чтобы избежать неоднозначности при сборке.

Приоритет разрешения модулей

Плагины onResolve выполняются до стандартного резолвинга esbuild. Это означает:

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

Структура приоритетов:

  1. Плагины onResolve
  2. Встроенный резолвер esbuild
  3. Node.js resolution (в режиме совместимости)

Конфликты и перекрытие правил

При пересечении alias-правил возможны неоднозначности:

{
  "@app": "./src/app",
  "@app/utils": "./src/shared/utils"
}

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

Практика:

  • сначала более специфичные правила
  • затем общие префиксы

Поддержка TypeScript и редакторов

Alias в esbuild не влияет автоматически на TypeScript. Для синхронизации требуется настройка tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

Без этой настройки IDE и компилятор TypeScript будут расходиться с esbuild.

Работа с ESM и CommonJS

Alias одинаково применяется как для ESM, так и для CommonJS модулей, поскольку обработка происходит до определения формата модуля.

Примеры:

import x from "@utils/math";
const y = require("@utils/math");

Оба варианта проходят через один механизм onResolve.

Вложенные alias и композиция

В сложных проектах alias может ссылаться на другие alias-пути. В таком случае требуется нормализация, чтобы избежать каскадных замен.

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

const aliases = {
  "@models": path.resolve("./src/models"),
  "@services": path.resolve("./src/services"),
};

Использование namespace для виртуальных alias

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

build.onResolve({ filter: /^virtual:/ }, () => ({
  namespace: "virtual-modules",
  path: "virtual",
}));

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

Ограничения механизма alias

Поскольку alias реализуется через плагины:

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

Тем не менее гибкость системы позволяет реализовать поведение, аналогичное любым bundler-решениям, включая многоуровневые пространства имён и динамическую маршрутизацию модулей.