В системе сборки Webpack загрузчики (loaders) представляют собой цепочку преобразований, через которую проходит каждый импортируемый модуль до попадания в финальный бандл. Любой файл в проекте — JavaScript, TypeScript, CSS, изображения, шрифты или даже произвольные форматы — рассматривается как модуль, который может быть преобразован в JavaScript-представление.
Механизм загрузчиков построен вокруг принципа композиционного преобразования: каждый loader получает результат предыдущего и возвращает новое представление модуля. Это позволяет выстраивать сложные цепочки обработки исходного кода без изменения ядра Webpack.
Перед тем как модуль попадает в граф зависимостей, Webpack проходит несколько стадий обработки:
Загрузчики включаются на этапе обработки модулей и выполняются до того, как Webpack начнёт их включение в бандл. Важно понимать, что loader не изменяет граф зависимостей напрямую — он трансформирует содержимое модулей.
Для одного файла может быть задано несколько loaders. Они выполняются в строгом порядке:
use)rules, если используется
несколько правил)Пример:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};
Здесь сначала выполняется css-loader, затем результат
передаётся в style-loader.
Логика цепочки:
исходный CSS
↓ css-loader
JS-модуль с экспортом стилей
↓ style-loader
вставка стилей в DOM
Каждый loader получает входные данные как строку или buffer и возвращает JavaScript-код или модифицированный ресурс.
Loader — это функция, которая принимает содержимое модуля:
module.exports = function(source) {
return source;
};
source — строка или Buffer, содержащая содержимое
файла.
Возвращаемое значение должно быть:
this.callbackthis.asyncПример асинхронного loader:
module.exports = function(source) {
const callback = this.async();
setTimeout(() => {
const result = source.replace(/foo/g, 'bar');
callback(null, result);
}, 100);
};
Каждый loader выполняется в специальном контексте this,
содержащем важные API:
Путь к обрабатываемому файлу.
console.log(this.resource);
Параметры loader (устаревший способ, сейчас используется
getOptions из loader-utils или
this.getOptions()).
Синхронная или асинхронная передача результата:
this.callback(null, transformedSource, sourceMap);
Переключение на асинхронный режим:
const callback = this.async();
Указание, можно ли кешировать результат:
this.cacheable(true);
Каждый loader может иметь две фазы:
Pitch-функции выполняются слева направо, в обратном порядке относительно основного выполнения.
module.exports.pitch = function(remainingRequest) {
return "code";
};
Если pitch возвращает значение, цепочка выполнения нормальных loaders прерывается.
Модель выполнения:
pitch loader1 → pitch loader2 → pitch loader3
→ normal loader3 → normal loader2 → normal loader1
Pitch используется для:
Порядок критически важен. Рассмотрим конфигурацию:
use: ['a-loader', 'b-loader', 'c-loader']
Фактический порядок:
pitch: a → b → c
normal: c → b → a
Если b-loader в pitch вернёт результат,
c-loader и его normal-часть не выполнятся.
Webpack поддерживает inline-указание loaders прямо в импорте:
import styles from 'css-loader!./styles.css';
Также доступны специальные префиксы:
! — отключает normal loaders из конфигурации!! — отключает все loaders из конфигурации-! — отключает pre-loadersПример:
import data from '-!babel-loader!./file.js';
Это позволяет точно управлять цепочкой обработки для конкретного модуля.
Загрузчики часто модифицируют код, поэтому важна поддержка source maps.
Loader может возвращать:
this.callback(null, code, map);
Source map позволяет сопоставить итоговый код с оригинальным файлом.
Типичный случай — Babel:
Без source maps отладка становится практически невозможной.
Любой loader может работать асинхронно, что важно при:
Пример:
module.exports = function(source) {
const callback = this.async();
someAsyncTransform(source, (err, result) => {
callback(err, result);
});
};
Webpack ждёт завершения всех async loaders перед продолжением сборки.
Loader — это модуль Node.js, экспортирующий функцию.
Минимальный пример:
module.exports = function(source) {
return source;
};
Расширенный вариант с опциями:
const { getOptions } = require('loader-utils');
module.exports = function(source) {
const options = getOptions(this);
const result = source.replace(
options.from,
options.to
);
return result;
};
Loader может обрабатывать не только строки, но и Buffers:
module.exports = function(source) {
return source.toString('utf8');
};
Также можно явно указать:
module.exports.raw = true;
Это означает, что вход будет Buffer, а не строка.
Webpack кеширует результаты loader’ов для ускорения повторных сборок. Для корректной работы важно:
this.addDependencyПример:
this.addDependency('/path/to/file');
Это заставляет Webpack пересобрать модуль при изменении внешнего файла.
style-loader
css-loader
postcss-loader
sass-loader
Фактическое преобразование:
sass-loader)postcss-loader)css-loader)style-loader)babel-loader
ts-loader
eslint-loader (устаревший подход)
Типичный сценарий:
Loaders не работают изолированно. Они являются частью системы модулей Webpack, где каждый модуль имеет:
Loader изменяет только source, не затрагивая dependency
graph напрямую.
При сборке Webpack выполняет следующие шаги для каждого модуля:
Каждый этап может быть прерван ошибкой или ранним возвратом результата.
Loaders работают на уровне модулей, а plugins — на уровне всей сборки. Часто они дополняют друг друга:
Пример: минификация через loader vs plugin — разные уровни контроля.
На производительность влияют:
Оптимизация достигается сокращением количества преобразований и использованием кеша Webpack.
Каждый loader передаёт дальше:
(source, sourceMap, meta)
Эта модель позволяет сохранять контекст трансформаций и строить сложные пайплайны обработки исходников, не теряя информацию о происхождении кода.