Подключение локального плагина

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

Parcel использует несколько категорий плагинов:

  • Transformer — преобразование файлов одного формата в другой;
  • Bundler — формирование итоговых пакетов ресурсов;
  • Optimizer — оптимизация выходных файлов;
  • Compressor — сжатие ресурсов;
  • Resolver — поиск и разрешение импортируемых модулей;
  • Reporter — вывод информации о процессе сборки;
  • Runtime — внедрение дополнительного кода во время выполнения;
  • Validator — проверка файлов перед сборкой.

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


Что такое локальный плагин

Локальный плагин — это модуль, расположенный непосредственно внутри проекта и подключаемый без публикации в npm-реестре.

Подобный подход применяется, когда необходимо:

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

Структура проекта может выглядеть следующим образом:

project/
├── src/
│   └── index.js
├── plugins/
│   └── custom-transformer.js
├── .parcelrc
├── package.json
└── parcel.config.js

В каталоге plugins обычно размещаются все пользовательские расширения.


Архитектура подключения

Подключение локального плагина состоит из нескольких этапов:

  1. Создание файла плагина.
  2. Реализация необходимого API.
  3. Регистрация плагина в конфигурации Parcel.
  4. Использование в процессе сборки.

Общая схема выглядит следующим образом:

Исходный файл
      ↓
Локальный плагин
      ↓
Parcel Pipeline
      ↓
Результат сборки

Каждый этап конвейера может использовать собственный набор плагинов.


Установка необходимых зависимостей

Для разработки плагинов используется пакет:

npm install @parcel/plugin

Или:

yarn add @parcel/plugin

Пакет содержит базовые классы и интерфейсы для создания расширений.


Создание простого Transformer-плагина

Наиболее распространённым типом пользовательского расширения является Transformer.

Создадим файл:

// plugins/custom-transformer.js

const { Transformer } = require("@parcel/plugin");

module.exports = new Transformer({
  async transform({ asset }) {
    let code = await asset.getCode();

    code = code.replace(
      /__BUILD_ENV__/g,
      process.env.NODE_ENV || "development"
    );

    asset.setCode(code);

    return [asset];
  }
});

Данный плагин:

  • получает содержимое файла;
  • ищет строку __BUILD_ENV__;
  • заменяет её текущим окружением;
  • возвращает модифицированный ресурс обратно в конвейер Parcel.

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

console.log("__BUILD_ENV__");

После сборки:

console.log("production");

Настройка файла .parcelrc

Parcel использует файл .parcelrc для определения набора активных плагинов.

Пример подключения локального Transformer:

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.js": ["./plugins/custom-transformer.js", "..."]
  }
}

Здесь:

  • ./plugins/custom-transformer.js — локальный модуль;
  • "..." — подключение остальных стандартных трансформеров.

Без оператора "..." стандартная цепочка обработки будет полностью заменена пользовательской.


Значение оператора “…”

В конфигурации Parcel данный оператор играет особую роль.

Пример:

{
  "transformers": {
    "*.js": ["./plugins/custom-transformer.js"]
  }
}

В этом случае Parcel использует исключительно локальный трансформер.

Другой вариант:

{
  "transformers": {
    "*.js": ["./plugins/custom-transformer.js", "..."]
  }
}

Теперь выполняется следующая последовательность:

custom-transformer
       ↓
стандартный Babel Transformer
       ↓
остальные внутренние плагины

Такой подход позволяет расширять существующий процесс сборки без полного переопределения поведения Parcel.


Передача данных через Asset API

Объект asset предоставляет доступ к содержимому и метаданным файла.

Часто используемые методы:

Получение кода

const code = await asset.getCode();

Замена содержимого

asset.setCode(newCode);

Получение пути

console.log(asset.filePath);

Изменение типа файла

asset.type = "css";

Чтение AST

const ast = await asset.getAST();

Установка AST

asset.setAST(ast);

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


Создание собственного Resolver

Resolver отвечает за поиск импортируемых модулей.

Пример:

const { Resolver } = require("@parcel/plugin");

module.exports = new Resolver({
  async resolve({ specifier }) {
    if (specifier.startsWith("@internal/")) {
      return {
        filePath: specifier.replace(
          "@internal/",
          process.cwd() + "/src/internal/"
        )
      };
    }

    return null;
  }
});

Подключение:

{
  "extends": "@parcel/config-default",
  "resolvers": ["./plugins/internal-resolver.js", "..."]
}

Теперь импорт:

import helper from "@internal/helper";

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


Создание Reporter-плагина

Reporter позволяет реагировать на события сборки.

Пример:

const { Reporter } = require("@parcel/plugin");

module.exports = new Reporter({
  report({ event }) {
    if (event.type === "buildSuccess") {
      console.log("Сборка успешно завершена");
    }
  }
});

Регистрация:

{
  "extends": "@parcel/config-default",
  "reporters": ["...", "./plugins/build-reporter.js"]
}

После успешной сборки сообщение будет выведено в консоль.


Использование логирования внутри плагина

Для отладки удобно использовать стандартный вывод:

console.log(asset.filePath);

Однако для серьёзных проектов предпочтительно применять встроенный логгер Parcel:

const { Logger } = require("@parcel/logger");

Logger.info({
  message: "Файл обработан"
});

Такой подход обеспечивает единообразный вывод информации во всех режимах работы сборщика.


Работа с несколькими расширениями файлов

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

Пример:

{
  "transformers": {
    "*.txt": ["./plugins/text-transformer.js"],
    "*.md": ["./plugins/text-transformer.js"]
  }
}

Один и тот же код сможет обслуживать различные форматы данных.


Создание цепочек локальных плагинов

В одной стадии конвейера допустимо использовать несколько пользовательских расширений.

Пример:

{
  "transformers": {
    "*.js": [
      "./plugins/env-transformer.js",
      "./plugins/license-transformer.js",
      "..."
    ]
  }
}

Порядок выполнения:

env-transformer
      ↓
license-transformer
      ↓
стандартные плагины Parcel

Каждый последующий плагин получает результат работы предыдущего.


Передача метаданных между плагинами

Parcel позволяет сохранять дополнительные данные в объекте ресурса.

Первый плагин:

asset.meta.author = "Developer";

Второй плагин:

console.log(asset.meta.author);

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


Создание Optimizer-плагина

Optimizer запускается после завершения основных преобразований.

Пример удаления комментариев:

const { Optimizer } = require("@parcel/plugin");

module.exports = new Optimizer({
  async optimize({ contents }) {
    const code = contents.toString();

    return {
      contents: code.replace(/\/\*[\s\S]*?\*\//g, "")
    };
  }
});

Регистрация:

{
  "extends": "@parcel/config-default",
  "optimizers": {
    "*.js": ["./plugins/comment-optimizer.js"]
  }
}

Теперь комментарии будут удаляться перед формированием итогового бандла.


Организация каталога локальных плагинов

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

Пример:

plugins/
├── transformers/
│   ├── env-transformer.js
│   ├── markdown-transformer.js
│   └── config-transformer.js
│
├── resolvers/
│   └── alias-resolver.js
│
├── optimizers/
│   └── js-optimizer.js
│
├── reporters/
│   └── build-reporter.js
│
└── utils/
    ├── logger.js
    └── parser.js

Подобная структура облегчает поддержку и развитие собственной инфраструктуры сборки.


Типичные ошибки при подключении локальных плагинов

Неверный путь

{
  "transformers": {
    "*.js": ["./plugin/custom.js"]
  }
}

Если каталог называется plugins, Parcel не сможет найти модуль.


Отсутствие экспорта

Неправильно:

const { Transformer } = require("@parcel/plugin");

new Transformer({
  async transform() {}
});

Правильно:

module.exports = new Transformer({
  async transform() {}
});

Забытый оператор “…”

Ошибка:

{
  "transformers": {
    "*.js": ["./plugins/custom.js"]
  }
}

Следствие:

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

Исправление:

{
  "transformers": {
    "*.js": ["./plugins/custom.js", "..."]
  }
}

Возврат неверного результата

Некорректно:

return asset;

Корректно:

return [asset];

Transformer должен возвращать массив ресурсов.


Практический сценарий: автоматическая подстановка версии приложения

Файл плагина:

const { Transformer } = require("@parcel/plugin");
const packageJson = require("../package.json");

module.exports = new Transformer({
  async transform({ asset }) {
    let code = await asset.getCode();

    code = code.replace(
      /__APP_VERSION__/g,
      packageJson.version
    );

    asset.setCode(code);

    return [asset];
  }
});

Исходный код:

console.log("Version:", "__APP_VERSION__");

После сборки:

console.log("Version:", "2.4.1");

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


Когда использовать локальные плагины

Локальные плагины особенно эффективны в следующих ситуациях:

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

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