Система плагинов является одной из ключевых особенностей Parcel. Большая часть функциональности сборщика реализована не внутри единого монолитного ядра, а через специализированные расширения, отвечающие за отдельные этапы обработки проекта. Такой подход обеспечивает модульность, гибкость и возможность адаптации процесса сборки под специфические требования.
Parcel использует несколько категорий плагинов:
Большинство разработчиков используют готовые плагины из экосистемы Parcel, однако в некоторых случаях требуется создание собственного локального расширения.
Локальный плагин — это модуль, расположенный непосредственно внутри проекта и подключаемый без публикации в npm-реестре.
Подобный подход применяется, когда необходимо:
Структура проекта может выглядеть следующим образом:
project/
├── src/
│ └── index.js
├── plugins/
│ └── custom-transformer.js
├── .parcelrc
├── package.json
└── parcel.config.js
В каталоге plugins обычно размещаются все
пользовательские расширения.
Подключение локального плагина состоит из нескольких этапов:
Общая схема выглядит следующим образом:
Исходный файл
↓
Локальный плагин
↓
Parcel Pipeline
↓
Результат сборки
Каждый этап конвейера может использовать собственный набор плагинов.
Для разработки плагинов используется пакет:
npm install @parcel/plugin
Или:
yarn add @parcel/plugin
Пакет содержит базовые классы и интерфейсы для создания расширений.
Наиболее распространённым типом пользовательского расширения является 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__;Использование:
console.log("__BUILD_ENV__");
После сборки:
console.log("production");
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 предоставляет доступ к содержимому и
метаданным файла.
Часто используемые методы:
const code = await asset.getCode();
asset.setCode(newCode);
console.log(asset.filePath);
asset.type = "css";
const ast = await asset.getAST();
asset.setAST(ast);
Asset API позволяет выполнять как простые текстовые преобразования, так и полноценную трансформацию синтаксических деревьев.
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 позволяет реагировать на события сборки.
Пример:
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 запускается после завершения основных преобразований.
Пример удаления комментариев:
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"]
}
}
Следствие:
Исправление:
{
"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 позволяет интегрировать пользовательскую логику непосредственно в конвейер сборки без публикации пакетов и без модификации исходного кода самого сборщика.