Загрузчик в Webpack представляет собой обычную функцию, которая получает исходный контент модуля и возвращает преобразованную версию этого контента. Несмотря на простоту концепции, загрузчики образуют один из ключевых механизмов расширения системы сборки, позволяя интерпретировать любые типы файлов как модули JavaScript.
Каждый загрузчик выполняется в контексте сборки и получает на вход строку или бинарные данные исходного модуля. На выходе он обязан вернуть результат преобразования, который может быть либо строкой JavaScript-кода, либо передан асинхронно через callback.
Сигнатура загрузчика выглядит следующим образом:
module.exports = function (source) {
return source;
};
Где source — содержимое файла после применения
предыдущих загрузчиков в цепочке.
Загрузчики могут быть синхронными и асинхронными, поддерживать дополнительные метаданные, передавать ошибки и управлять процессом компиляции.
Функция загрузчика не является изолированной. Webpack предоставляет
специальный контекст через this, который содержит
информацию о текущем модуле и инструменты управления процессом
трансформации.
Основные свойства контекста:
this.resource — путь к текущему файлуthis.context — директория модуляthis.query — параметры загрузчика (устаревший
формат)this.getOptions() — получение опций загрузчика (Webpack
5)this.emitFile() — генерация дополнительных файловthis.addDependency() — добавление зависимостейthis.cacheable() — управление кешированием
результатаПример использования:
module.exports = function (source) {
const resourcePath = this.resource;
this.addDependency(resourcePath);
return source;
};
Синхронный загрузчик возвращает результат напрямую:
module.exports = function (source) {
return source.replace('foo', 'bar');
};
Такой подход подходит для быстрых и детерминированных преобразований.
Если трансформация требует асинхронных операций (например, чтение
файлов или запросы), используется this.async():
module.exports = function (source) {
const callback = this.async();
setTimeout(() => {
const result = source.replace('foo', 'bar');
callback(null, result);
}, 100);
};
Функция callback принимает два аргумента:
error — ошибка, если она возниклаresult — преобразованный кодЗагрузчики выполняются справа налево (или снизу вверх в конфигурации). Это означает, что последний указанный loader обрабатывает исходный файл первым.
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: ['loader-a', 'loader-b']
}
]
}
};
Здесь выполнение будет:
Такой порядок позволяет строить цепочки преобразований, где каждый этап работает с результатом предыдущего.
Каждый загрузчик может содержать дополнительную функцию
pitch. Она выполняется до основного тела загрузчика и может
прервать дальнейшую цепочку.
module.exports.pitch = function (remainingRequest) {
return 'export default "skipped";';
};
Если pitch возвращает значение, выполнение остальных
загрузчиков для данного модуля прекращается.
Механизм pitch позволяет реализовывать сложные сценарии,
такие как кеширование, перехват или замена стандартной обработки.
Современный способ передачи параметров загрузчику — использование
this.getOptions().
module.exports = function (source) {
const options = this.getOptions();
const prefix = options.prefix || '';
return prefix + source;
};
Конфигурация:
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: {
loader: 'custom-loader',
options: {
prefix: 'LOG: '
}
}
}
]
}
};
Для валидации опций часто используется JSON Schema:
const schema = {
type: 'object',
properties: {
prefix: { type: 'string' }
}
};
module.exports = function (source) {
const options = this.getOptions(schema);
return options.prefix + source;
};
Загрузчик может не только преобразовывать код, но и генерировать новые ресурсы:
module.exports = function (source) {
this.emitFile('output.txt', source);
return `export default "generated";`;
};
Это полезно для генерации ассетов, сопутствующих модулю: изображений, текстов, CSS-файлов.
Загрузчик может объявлять зависимости, которые влияют на пересборку:
module.exports = function (source) {
this.addDependency('/path/to/config.json');
return source;
};
Если зависимость изменяется, Webpack пересобирает модуль.
Webpack может кешировать результат выполнения загрузчика, если он считается чистым:
module.exports = function (source) {
this.cacheable(true);
return source.trim();
};
Кеширование ускоряет повторные сборки, особенно в крупных проектах.
Ошибки могут быть выброшены синхронно или переданы через callback:
module.exports = function (source) {
throw new Error('Invalid content');
};
или
module.exports = function (source) {
const callback = this.async();
if (!source) {
callback(new Error('Empty file'));
return;
}
callback(null, source);
};
Webpack останавливает обработку текущего модуля при возникновении ошибки.
Некоторые загрузчики работают с бинарными данными. Для этого
используется режим raw:
module.exports = function (content) {
return content.toString('utf8');
};
module.exports.raw = true;
В этом случае content передается как
Buffer.
Цепочка загрузчиков представляет собой поток преобразований:
file → loader3 → loader2 → loader1 → bundle
Каждый загрузчик получает результат предыдущего, что позволяет строить модульные трансформации.
Важно учитывать, что:
Хорошая практика — выделять чистую трансформационную логику отдельно от Webpack API:
function transform(code, options) {
return code.replace(options.from, options.to);
}
module.exports = function (source) {
const options = this.getOptions();
return transform(source, options);
};
Это упрощает тестирование и повторное использование.
Хотя Webpack loader API не использует промисы напрямую, асинхронную логику можно организовать через их обертку:
module.exports = function (source) {
const callback = this.async();
Promise.resolve(source)
.then((code) => code.toUpperCase())
.then((result) => callback(null, result))
.catch((err) => callback(err));
};
Такой подход полезен при интеграции с современными API.
Webpack предоставляет доступ к полной цепочке запроса:
module.exports = function (source) {
const request = this.request;
const resource = this.resource;
return source;
};
Это позволяет реализовывать conditional-обработку в зависимости от источника модуля.
Фаза pitch выполняется до основной функции загрузчика и
имеет обратный порядок вызова:
loader1.pitch → loader2.pitch → loader3.pitch
loader3 → loader2 → loader1
Эта модель позволяет реализовать перехватывающие цепочки.
Типичный loader в реальных проектах включает:
module.exports = function (source) {
const options = this.getOptions();
this.cacheable();
try {
const result = source.replace(options.search, options.replace);
return result;
} catch (e) {
throw e;
}
};
Существенное различие между версиями заключается в:
this.getOptions)Webpack 5 делает упор на строгую изоляцию и предсказуемость поведения загрузчиков.
При компиляции Webpack:
Каждый этап влияет на финальную структуру бандла и возможность дальнейшего tree-shaking и оптимизации.