API плагина: структура и экспорт

Архитектура плагинов в Parcel

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

Плагин в Parcel представляет собой JavaScript-модуль, экспортирующий функции-хуки. Эти хуки группируются по типам задач:

  • резолвинг (resolver)
  • трансформация (transformer)
  • генерация (generator)
  • оптимизация (optimizer)
  • упаковка (packager)
  • нативные интеграции (runtime, bundler hooks)

Каждый тип плагина подключается к определённой фазе пайплайна сборки.


Базовая форма экспорта плагина

Плагин в Parcel экспортируется как объект или набор функций, где ключи соответствуют хукам API.

Типичный вариант ESM-экспорта:

export default {
  name: "custom-plugin",

  async loadConfig({ config }) {
    return {};
  },

  transform({ asset }) {
    return [asset];
  }
};

Допускается также экспорт функций напрямую, если плагин реализует единственный хук:

export default function transformer({ asset }) {
  return [asset];
}

Parcel определяет тип плагина по наличию соответствующих методов.


Именование и идентификация плагина

Каждый плагин обязан предоставлять уникальное имя через свойство name. Это имя используется:

  • в логах сборки
  • в системе диагностики ошибок
  • при конфликте зависимостей
  • в трассировке пайплайна
export default {
  name: "parcel-transformer-svg",
};

Имя не влияет на функциональность, но критично для отладки и экосистемной совместимости.


Хуки и их контракт

Плагин API строится на строгих контрактах функций. Каждый хук принимает объект контекста и возвращает строго определённый результат.

Resolver API

Resolver отвечает за определение пути модуля.

export async function resolve({ dependency }) {
  if (dependency.specifier === "my-lib") {
    return {
      filePath: "/absolute/path/to/my-lib/index.js",
    };
  }
}

Основные свойства контекста:

  • dependency.specifier — строка импорта
  • dependency.resolveFrom — исходный модуль
  • options — конфигурация резолвера

Результат всегда должен содержать filePath, иначе резолв считается неуспешным.


Transformer API

Transformer — центральный тип плагина. Он изменяет содержимое модулей.

export async function transform({ asset }) {
  const code = await asset.getCode();

  const transformed = code.replace("const", "let");

  asset.setCode(transformed);

  return [asset];
}

Ключевые принципы:

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

Generator API

Generator отвечает за финальную генерацию кода.

export function generate({ asset }) {
  return {
    code: asset.generatedCode,
    map: asset.map,
  };
}

Используется для:

  • формирования итогового JavaScript
  • генерации CSS/HTML
  • подключения source maps

Optimizer API

Optimizer применяется после генерации бандла.

export function optimize({ bundle }) {
  return {
    contents: minify(bundle.contents),
  };
}

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

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

Структура экспортируемого объекта плагина

Полный плагин может содержать несколько типов хуков одновременно.

export default {
  name: "complex-plugin",

  async resolve() {},
  async transform() {},
  async generate() {},
  async optimize() {},
};

Parcel агрегирует все методы и регистрирует их в соответствующих стадиях pipeline.


Работа с конфигурацией

Плагин может загружать конфигурацию проекта через специальный хук:

export async function loadConfig({ config }) {
  const result = await config.getConfig([
    "my-plugin.config.json",
  ]);

  return result.contents;
}

Используется для:

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

Асинхронность и поток выполнения

Все основные хуки поддерживают async/await. Parcel строит граф зависимостей и параллелит выполнение там, где это возможно.

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

  • resolver может выполняться параллельно
  • transform часто запускается конкурентно
  • generator и optimizer чаще выполняются последовательно

Работа с Asset API

asset — ключевая абстракция внутри трансформеров.

Основные методы:

asset.getCode()
asset.setCode(code)
asset.getMap()
asset.setMap(map)
asset.addDependency(dep)

Пример модификации:

export async function transform({ asset }) {
  const code = await asset.getCode();

  if (code.includes("debug")) {
    asset.setCode(code.replace("debug", ""));
  }

  return [asset];
}

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

Плагин может динамически добавлять зависимости:

asset.addDependency({
  specifier: "./utils.js",
  kind: "esm",
});

Типы зависимостей:

  • esm — ES modules
  • commonjs
  • url
  • worker
  • dynamic

Это влияет на граф сборки и код-сплиттинг.


Экспорт нескольких плагинов из одного пакета

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

export const resolver = {
  name: "resolver-plugin",
  resolve() {},
};

export const transformer = {
  name: "transformer-plugin",
  transform() {},
};

Parcel регистрирует каждый экспорт как отдельный плагин.


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

Поддерживаются оба формата:

module.exports = {
  name: "cjs-plugin",
  transform() {},
};

или

export default {
  name: "esm-plugin",
  transform() {},
};

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


Контекст выполнения плагина

Каждый хук получает контекст:

  • logger — логирование
  • options — параметры сборки
  • filePath — путь к модулю
  • env — переменные окружения

Пример:

export function transform({ logger, asset }) {
  logger.info({ message: "transforming asset" });

  return [asset];
}

Ошибки и диагностика

Ошибки должны выбрасываться явно:

throw new Error("Unsupported syntax");

Parcel оборачивает их в диагностический формат:

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

Также поддерживаются предупреждения:

logger.warn({ message: "deprecated API used" });

Ограничения и контракт поведения

Плагины должны соблюдать ряд правил:

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

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


Типовая структура плагин-пакета

parcel-plugin-example/
  package.json
  src/
    index.js
    resolver.js
    transformer.js

package.json:

{
  "name": "parcel-plugin-example",
  "main": "src/index.js",
  "peerDependencies": {
    "parcel": "^2.0.0"
  }
}

Регистрация хуков в pipeline

Parcel автоматически регистрирует хуки по их именам. Нет необходимости вручную описывать тип плагина.

Система распознаёт:

  • resolve → resolver
  • transform → transformer
  • generate → generator
  • optimize → optimizer

Это позволяет минимизировать конфигурацию и снижает вероятность ошибок интеграции.


Взаимодействие плагинов между собой

Плагины не вызывают друг друга напрямую. Вместо этого используется общий pipeline:

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

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