В основе Parcel лежит модульная система плагинов, где ключевую роль играют трансформеры (transformers). Именно они отвечают за преобразование входных файлов в формат, пригодный для дальнейшей сборки и бандлинга. Любой файл, проходящий через пайплайн Parcel, может быть изменён одним или несколькими трансформерами в зависимости от его типа, расширения и настроек проекта.
Трансформер в Parcel — это модуль, который принимает исходный код и возвращает структурированное представление в виде AST (Abstract Syntax Tree) или модифицированного кода. На практике чаще используется кодовая трансформация без явной работы с AST, однако поддержка AST-инструментов также встроена.
Parcel использует строгую контрактную модель: каждый трансформер должен соответствовать API плагинов и явно объявлять, какие типы файлов он обрабатывает.
Трансформер в Parcel представляет собой объект с набором методов, где
обязательным является метод transform. Минимальная
реализация выглядит следующим образом:
export default {
transform({ asset }) {
const code = asset.getCode();
const transformedCode = code.replace(/console\.log/g, 'logger.log');
asset.setCode(transformedCode);
return [asset];
}
};
Ключевые элементы:
asset — объект, представляющий файл в системе
ParcelgetCode() — получение исходного содержимого файлаsetCode() — запись изменённого содержимогоТакой трансформер может быть подключён к конфигурации Parcel через
.parcelrc.
Parcel использует декларативную систему конфигурации плагинов. Для подключения трансформера используется файл конфигурации:
{
"extends": "@parcel/config-default",
"transformers": {
"*.txt": ["./transformers/text-transformer.js"]
}
}
В этом примере все .txt файлы будут проходить через
кастомный трансформер.
Важно, что порядок трансформеров имеет значение: каждый следующий получает результат предыдущего.
Объект asset является центральной абстракцией Parcel. Он
содержит не только код, но и метаданные файла.
Основные методы:
asset.getCode() — исходный текстasset.setCode(code) — обновление содержимогоasset.type — тип файла (js, css, txt и т.д.)asset.filePath — путь к файлуasset.meta — пользовательские метаданныеПример использования метаданных:
export default {
transform({ asset }) {
const code = asset.getCode();
asset.meta.transformedAt = Date.now();
asset.setCode(code.toUpperCase());
return [asset];
}
};
Метаданные сохраняются между этапами сборки и могут использоваться другими плагинами.
Parcel поддерживает асинхронные трансформеры, что важно для работы с сетью, файловой системой или тяжёлыми вычислениями.
export default {
async transform({ asset }) {
const code = await asset.getCode();
const response = await fetch('https://api.example.com/transform', {
method: 'POST',
body: code
});
const result = await response.text();
asset.setCode(result);
return [asset];
}
};
Асинхронная модель исполнения позволяет не блокировать основной поток сборки и масштабировать пайплайн.
Трансформер может возвращать несколько файлов, создавая новые активы на основе одного входного.
export default {
transform({ asset }) {
const code = asset.getCode();
const minified = code.replace(/\s+/g, '');
const minAsset = asset.clone();
minAsset.setCode(minified);
minAsset.filePath = asset.filePath.replace('.js', '.min.js');
return [asset, minAsset];
}
};
Такой подход используется для генерации:
Для сложных трансформаций используется AST. Parcel предоставляет интеграцию с Babel и другими парсерами.
Пример трансформации через AST:
import * as babel from '@babel/core';
export default {
transform({ asset }) {
const code = asset.getCode();
const result = babel.transformSync(code, {
plugins: [
function customPlugin() {
return {
visitor: {
Identifier(path) {
if (path.node.name === 'debug') {
path.node.name = 'logger';
}
}
}
};
}
]
});
asset.setCode(result.code);
return [asset];
}
};
AST-подход обеспечивает:
Parcel позволяет ограничивать применение трансформеров по типу или содержимому файла.
export default {
transform({ asset }) {
if (asset.type !== 'js') {
return [asset];
}
const code = asset.getCode();
if (!code.includes('console')) {
return [asset];
}
asset.setCode(code.replace(/console/g, 'log'));
return [asset];
}
};
Такой подход снижает избыточную обработку и ускоряет сборку.
Трансформеры могут добавлять новые зависимости динамически.
export default {
transform({ asset }) {
asset.addDependency({
specifier: './utils.js',
specifierType: 'esm'
});
const code = asset.getCode();
asset.setCode(code);
return [asset];
}
};
Это позволяет строить динамические графы модулей прямо на этапе трансформации.
Parcel ожидает, что трансформер либо вернёт корректный результат, либо выбросит исключение.
export default {
transform({ asset }) {
try {
const code = asset.getCode();
if (!code) {
throw new Error('Empty file');
}
asset.setCode(code);
return [asset];
} catch (err) {
throw new Error(`Transform error in ${asset.filePath}: ${err.message}`);
}
}
};
Ошибки в трансформерах прерывают сборку и отображаются в терминале с указанием файла.
Parcel автоматически кэширует результаты трансформаций на основе:
Для корректной работы трансформера важно учитывать детерминированность:
export default {
transform({ asset }) {
const code = asset.getCode();
// Нежелательно: недетерминированный результат
const randomSuffix = Math.random();
asset.setCode(code + randomSuffix);
return [asset];
}
};
Такой код нарушает кэширование и приводит к нестабильным сборкам.
Производительность трансформеров критична в больших проектах. Основные принципы оптимизации:
Пример раннего выхода:
export default {
transform({ asset }) {
const code = asset.getCode();
if (!code.includes('TODO')) {
return [asset];
}
asset.setCode(code.replace(/TODO/g, ''));
return [asset];
}
};
Parcel последовательно применяет трансформеры, формируя цепочку обработки:
Исходный файл → Transformer A → Transformer B → Transformer C → Bundler
Каждый трансформер должен быть независимым и не полагаться на внутреннее состояние других плагинов.
Пример конфигурации:
{
"transformers": {
"*.js": [
"./transformers/remove-debug.js",
"./transformers/replace-api.js",
"./transformers/minify.js"
]
}
}
Трансформеры применяются не только для модификации кода, но и для:
Пример feature flag трансформации:
export default {
transform({ asset }) {
const code = asset.getCode();
const isProd = process.env.NODE_ENV === 'production';
const result = code.replace(/__DEV__/g, String(!isProd));
asset.setCode(result);
return [asset];
}
};