Хук load

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

Жизненный цикл обработки модуля в Rollup можно представить как последовательность стадий:

  1. Разрешение идентификатора модуля (resolveId)
  2. Загрузка содержимого модуля (load)
  3. Трансформация кода (transform)
  4. Генерация бандла

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

Важно понимать, что Rollup не накладывает ограничений на источник данных: это может быть файл на диске, виртуальный модуль, результат API-запроса или динамически сгенерированный код.

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

Хук load имеет следующую сигнатуру:

load(id: string) => string | null | void | Promise<string | null | void>

Параметр id представляет собой идентификатор модуля, полученный на этапе resolveId. Это может быть:

  • абсолютный путь к файлу
  • виртуальный идентификатор (например, virtual:module)
  • модифицированный путь после работы других плагинов

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

  • string — содержимое модуля
  • null или undefined — передача управления следующему плагину
  • Promise — асинхронная загрузка содержимого

Основной принцип работы

Rollup вызывает хук load последовательно по цепочке плагинов до тех пор, пока один из них не вернёт содержимое модуля. Как только это происходит, цепочка прерывается, и результат передаётся на этап transform.

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

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

Приоритет выполнения плагинов

Порядок выполнения хука load определяется порядком подключения плагинов:

  • плагины выполняются сверху вниз
  • первый плагин, вернувший строку, «захватывает» модуль
  • последующие плагины load для этого модуля не вызываются

Это создаёт предсказуемую модель приоритета, позволяющую переопределять поведение загрузки.

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

Наиболее распространённый сценарий — создание виртуальных модулей:

export default function virtualModulePlugin() {
  return {
    name: 'virtual-module',

    load(id) {
      if (id === 'virtual:example') {
        return `export const value = 42;`;
      }
      return null;
    }
  };
}

В этом примере модуль virtual:example не существует в файловой системе, но полностью создаётся на этапе load.

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

Хук load почти всегда используется совместно с resolveId. Их связка позволяет реализовывать виртуальные модули и псевдонимы:

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

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

resolveId(source) {
  if (source === 'my-virtual') {
    return 'virtual:my-virtual';
  }
  return null;
},

load(id) {
  if (id === 'virtual:my-virtual') {
    return 'export default "data";';
  }
  return null;
}

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

Асинхронная загрузка

Хук load поддерживает асинхронность, что делает его пригодным для работы с удалёнными ресурсами или файловыми системами:

load(id) {
  if (id.startsWith('remote:')) {
    return fetch(`https://example.com/modules/${id.slice(7)}`)
      .then(res => res.text());
  }
  return null;
}

Асинхронная модель позволяет интегрировать Rollup с внешними API, базами данных или CDN.

Условная загрузка модулей

Одной из сильных сторон load является возможность условной генерации кода:

  • по окружению
  • по конфигурации
  • по типу модуля
  • по метаданным идентификатора

Пример:

load(id) {
  if (id.includes('debug')) {
    return `console.log("Debug module loaded"); export default {};`;
  }

  if (id.endsWith('.special.js')) {
    return `export const special = true;`;
  }

  return null;
}

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

Виртуальные модули — один из наиболее важных кейсов использования load. Они позволяют:

  • генерировать код на лету
  • избегать файловой системы
  • создавать API-обёртки
  • внедрять конфигурационные данные

Примеры применения:

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

Кэширование и повторные вызовы

Rollup может вызывать load несколько раз в рамках разных сборок или при инвалидации кэша. Поэтому:

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

Нарушение этих принципов приводит к нестабильной сборке.

Ошибки и обработка исключений

Если load выбрасывает исключение, Rollup прерывает процесс сборки с ошибкой. Это поведение используется для:

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

Пример:

load(id) {
  if (!id.startsWith('/allowed/')) {
    throw new Error('Access denied');
  }
  return fs.readFileSync(id, 'utf-8');
}

Взаимодействие с файловой системой

Если load возвращает null, Rollup переходит к стандартному чтению файлов:

  • используется fs.readFile
  • применяется кодировка UTF-8
  • путь берётся из id

Это означает, что load полностью опционален для обычных файловых модулей и нужен только при необходимости переопределения поведения.

Производительность и оптимизация

При использовании load важно учитывать влияние на производительность сборки:

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

Оптимальные практики:

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

Совместимость с другими хуками

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

  • resolveId — определение модуля
  • transform — модификация кода
  • shouldTransformCachedModule — управление кэшем

В этой связке load выполняет роль источника данных, а не преобразователя.

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

Если несколько плагинов реализуют load, они образуют цепочку:

  1. первый плагин получает id
  2. если возвращает null, вызывается следующий
  3. процесс продолжается до первого успешного результата

Это позволяет реализовывать:

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

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

На практике часто встречаются следующие проблемы:

  • возврат undefined вместо строки в сложных сценариях
  • некорректное сравнение id
  • отсутствие return null, из-за чего цепочка прерывается
  • неучтённые виртуальные идентификаторы
  • блокирующие операции в асинхронных проектах

Эти ошибки приводят либо к пропуску модулей, либо к нестабильной сборке.

Архитектурное значение хука

load формирует фундамент модели модулей Rollup. Он позволяет:

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

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