Хук resolveId

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

Роль в процессе построения графа модулей

Во время анализа входной точки Rollup последовательно обходит импорты и для каждого выражения вида import ... from '...' запускает цепочку разрешения идентификатора модуля. В этот процесс может вмешиваться плагин через resolveId, изменяя стандартное поведение.

Основная задача хука — преобразование строкового идентификатора импорта в:

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

Именно здесь решается, будет ли модуль включён в бандл, заменён или проигнорирован.

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

Хук resolveId имеет следующую логическую форму:

resolveId(source, importer, options)

Где:

  • source — строка импорта, указанная в коде ('react', './utils', 'fs')
  • importer — путь к модулю, из которого выполняется импорт
  • options — дополнительные параметры разрешения (например, условия Node ESM)

Возвращаемое значение определяет дальнейшее поведение Rollup:

  • string — новый путь к модулю
  • { id, external } — объект с явным указанием внешней зависимости
  • null или undefined — передача управления следующему резолверу

Цепочка вызовов и приоритет плагинов

Все плагины с реализацией resolveId выстраиваются в строгую последовательность. Rollup вызывает их по очереди до тех пор, пока один из них не вернёт ненулевой результат.

Порядок обработки:

  1. Встроенный механизм Rollup
  2. Пользовательские плагины в порядке объявления
  3. Финальный fallback-резолвер

Если один плагин возвращает значение, цепочка может быть прервана, и дальнейшие resolveId не вызываются.

Перехват и переопределение модулей

Одной из наиболее распространённых задач resolveId является подмена модулей. Это используется для:

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

Пример логики:

export default function myPlugin() {
  return {
    name: 'my-plugin',

    resolveId(source) {
      if (source === 'env') {
        return '\0virtual:env';
      }
      return null;
    }
  };
}

Здесь строка env заменяется на виртуальный модуль, идентифицируемый префиксом \0, который Rollup трактует как внутренний.

Виртуальные модули и namespace-идентификаторы

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

Для их обозначения часто используется соглашение:

  • \0 префикс — внутренний модуль Rollup
  • virtual: — семантический нейминг

resolveId в этом случае играет роль генератора идентификатора:

resolveId(source) {
  if (source === 'config') {
    return '\0virtual:config';
  }
}

Далее этот идентификатор обрабатывается через load.

Управление внешними зависимостями

Хук может явно помечать модуль как внешний, исключая его из бандла:

resolveId(source) {
  if (source === 'lodash') {
    return { id: 'lodash', external: true };
  }
}

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

  • уменьшить размер сборки
  • оставить зависимость для runtime
  • интегрироваться с CDN или внешними системами модулей

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

Различие между относительными и пакетными импортами

Внутри resolveId часто реализуется логика различения типов импортов:

  • относительные: ./, ../
  • абсолютные пакеты: react, lodash
  • алиасы: @src/utils

Пример обработки алиасов:

resolveId(source) {
  if (source.startsWith('@src/')) {
    return source.replace('@src/', '/project/src/');
  }
}

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

Асинхронный resolveId

resolveId может быть асинхронным, что важно при:

  • обращении к файловой системе
  • запросах к API
  • вычислении конфигурации на лету
async resolveId(source) {
  const result = await fetchConfig(source);
  return result?.path || null;
}

Асинхронность интегрируется в общий pipeline Rollup без блокировки сборки.

Управление importer и контекстом

Второй аргумент importer критически важен для контекстного разрешения:

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

Пример:

resolveId(source, importer) {
  if (source === './config') {
    if (importer.includes('admin')) {
      return '/config/admin.js';
    }
    return '/config/default.js';
  }
}

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

Options и расширенное поведение

Третий аргумент options содержит дополнительные параметры, связанные с резолвингом:

  • условия ESM (conditions)
  • предпочтения платформы
  • дополнительные флаги поиска

Это позволяет адаптировать поведение под Node.js ESM-алгоритм или кастомные среды.

Пример использования:

resolveId(source, importer, options) {
  if (options?.isEntry) {
    return `/entry/${source}`;
  }
}

Влияние на граф зависимостей

Каждый результат resolveId напрямую влияет на структуру dependency graph:

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

Ошибки на этом этапе приводят к:

  • дублированию модулей
  • циклическим зависимостям
  • некорректной tree-shaking оптимизации

Совместная работа с load

resolveId почти всегда используется совместно с load:

  • resolveId определяет идентификатор
  • load возвращает содержимое модуля

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

resolveId() -> '\0virtual:data'
load(id) -> 'export const data = 42;'

Приоритет и контроль потока

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

Возврат строки или объекта, наоборот, фиксирует результат и завершает резолвинг.

Практическое значение в архитектуре Rollup

resolveId фактически определяет, как Rollup воспринимает мир модулей:

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

Через этот хук реализуются:

  • alias-системы
  • интеграции с нестандартными источниками кода
  • условные сборки
  • оптимизации структуры бандла

Он находится в начале цепочки трансформации и задаёт фундамент всей последующей обработки модулей.