Контекст плагина: this.parse, this.resolve, this.load

Контекст плагина Rollup представляет собой объект, доступный внутри большинства хуков плагина через this, и содержит набор низкоуровневых API для взаимодействия с системой сборки. Среди наиболее важных методов этого контекста находятся this.parse, this.resolve и this.load, которые позволяют плагину вмешиваться в этапы анализа, разрешения модулей и загрузки исходного кода.

В процессе сборки Rollup последовательно обрабатывает модули через цепочку хуков: resolveId, load, transform и другие. Однако внутри этих хуков часто требуется доступ к внутренним механизмам самого бандлера. Контекст плагина предоставляет такой доступ, позволяя выполнять операции, аналогичные внутренним шагам Rollup, но в управляемом и расширяемом виде.

Методы контекста не являются независимыми утилитами — они работают в рамках текущего графа модулей, учитывают кеширование, зависимости и конфигурацию сборки.


this.parse: разбор исходного кода в AST

Метод this.parse используется для преобразования строки исходного кода в AST (Abstract Syntax Tree). Это тот же этап, который Rollup выполняет при анализе модулей, но доступный напрямую внутри плагина.

Сигнатура:

this.parse(code: string, acornOptions?: object) => ESTree.Program

Назначение

Основная задача this.parse — дать плагину возможность анализировать структуру кода без необходимости вручную подключать парсер (например, Acorn). Rollup использует Acorn под капотом, поэтому результат соответствует ESTree-совместимому AST.

Это особенно важно для плагинов, которые:

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

Особенности работы

  • Используется тот же парсер, что и в Rollup, что обеспечивает консистентность AST.
  • Поддерживает опции Acorn (например, ecmaVersion, sourceType).
  • Не учитывает трансформации других плагинов — анализируется исходный или текущий код на момент вызова.

Типичный сценарий использования

Плагин может извлечь все импортируемые модули:

transform(code) {
  const ast = this.parse(code);

  for (const node of ast.body) {
    if (node.type === 'ImportDeclaration') {
      console.log(node.source.value);
    }
  }
}

Ограничения

  • Не предназначен для полноценной трансформации кода.
  • Не заменяет transform-хук.
  • Не кэширует результаты отдельно от Rollup, поэтому частые вызовы могут влиять на производительность.

this.resolve: разрешение модулей в графе зависимостей

Метод this.resolve выполняет логическое разрешение идентификатора модуля в реальный путь или виртуальный идентификатор внутри графа Rollup. Это аналог внутреннего процесса, который происходит в resolveId.

Сигнатура:

this.resolve(source: string, importer?: string, options?: object) => Promise<ResolvedId | null>

Назначение

this.resolve позволяет плагину:

  • определить, куда указывает импорт;
  • проверить, является ли модуль внешним;
  • получить финальный идентификатор модуля в графе;
  • эмулировать поведение import внутри сборщика.

Поведение и результат

Результатом вызова является объект вида:

{
  id: string,
  external?: boolean,
  resolvedBy?: string
}

или null, если модуль не может быть разрешён.

Важные аспекты

  • Учитывает все плагины, участвующие в resolveId.

  • Поддерживает опции, влияющие на поведение резолвинга:

    • skipSelf — исключает текущий плагин из цепочки разрешения;
    • isEntry — указывает, что модуль является входной точкой;
    • custom — дополнительные пользовательские параметры.

Применение в плагинах

Часто используется для анализа зависимостей:

async transform(code, id) {
  const ast = this.parse(code);

  for (const node of ast.body) {
    if (node.type === 'ImportDeclaration') {
      const resolved = await this.resolve(node.source.value, id);

      if (resolved) {
        console.log('Resolved:', resolved.id);
      }
    }
  }
}

Работа с виртуальными модулями

this.resolve особенно важен при работе с виртуальными модулями (virtual: схемы), где стандартное файловое разрешение не применяется.


this.load: принудительная загрузка модуля

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

Сигнатура:

this.load(options: { id: string }) => Promise<LoadResult | null>

Назначение

Основные задачи this.load:

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

Результат выполнения

Результат обычно имеет форму:

{
  code: string,
  map?: SourceMap,
  ast?: object
}

или null, если модуль не может быть загружен.

Внутренний механизм

При вызове:

  1. Rollup проверяет кеш загрузки.
  2. Запускает цепочку load-хуков плагинов.
  3. Возвращает первый непустой результат.
  4. Применяет кеширование результата.

Использование в сложных плагинах

Часто применяется для построения метаплагинов, анализирующих или модифицирующих другие модули:

async buildStart() {
  const result = await this.load({ id: 'src/index.js' });

  if (result) {
    console.log(result.code);
  }
}

Особенности

  • Может вызывать цепочки load-хуков рекурсивно.
  • Подвержен влиянию кеша Rollup.
  • Следует осторожно использовать, чтобы избежать повторной загрузки одних и тех же модулей.

Взаимодействие this.parse, this.resolve и this.load

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

  • this.parse отвечает за анализ структуры кода;
  • this.resolve определяет связи между модулями;
  • this.load предоставляет доступ к содержимому модулей через систему Rollup.

Комбинирование этих методов позволяет:

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

Типичные паттерны использования

Анализ импортов с резолвингом

transform(code, id) {
  const ast = this.parse(code);

  for (const node of ast.body) {
    if (node.type === 'ImportDeclaration') {
      this.resolve(node.source.value, id).then(resolved => {
        if (resolved && !resolved.external) {
          console.log(resolved.id);
        }
      });
    }
  }
}

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

async buildStart() {
  const entry = await this.resolve('./src/main.js');

  if (entry) {
    const mod = await this.load({ id: entry.id });
    console.log(mod?.code);
  }
}

Синтаксический анализ без трансформации

transform(code) {
  const ast = this.parse(code);

  return null;
}

Подводные аспекты и ограничения

1. Рекурсивные вызовы

Комбинация this.load и this.resolve может приводить к рекурсивным цепочкам загрузки, если плагин некорректно обрабатывает зависимости.

2. Производительность

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

3. Кэширование

Rollup активно кэширует результаты load, но вызовы через this.load могут обходить некоторые оптимизации, если используются нестандартные id или виртуальные модули.

4. Зависимость от порядка плагинов

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

5. AST-несовместимость при трансформациях

this.parse работает с текущим состоянием кода, но не учитывает будущие трансформации других плагинов, что может приводить к расхождениям между AST и финальным кодом.