NormalModuleReplacementPlugin и ContextReplacementPlugin относятся к классу продвинутых инструментов Webpack, предназначенных для тонкого управления процессом резолва модулей и контекстных зависимостей. Их применение выходит за рамки стандартной сборки и используется в сценариях, где требуется динамическая подмена модулей, сокращение объёма бандла или контроль над контекстным импортом, который иначе привёл бы к избыточному включению файлов.
Оба плагина работают на этапе построения графа зависимостей и влияют на то, какие модули попадут в итоговый бандл, однако делают это разными механизмами. NormalModuleReplacementPlugin оперирует конкретными модулями, тогда как ContextReplacementPlugin управляет целыми контекстными областями, формируемыми динамическими выражениями require или import.
NormalModuleReplacementPlugin используется для замены одного модуля другим в процессе сборки. Он перехватывает момент резолва модуля и позволяет подменить путь или полностью изменить модуль, который будет включён в граф зависимостей.
Во время обхода зависимостей Webpack создаёт объекты модулей на основе импортов. Когда встречается импорт:
import config from './config';
Webpack резолвит путь ./config в конкретный файл.
NormalModuleReplacementPlugin позволяет вмешаться в этот процесс и
заменить целевой модуль до его добавления в граф.
Плагин подписывается на внутренние хуки компиляции и проверяет каждый модуль на соответствие заданному регулярному выражению или условию. При совпадении путь переписывается.
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.NormalModuleReplacementPlugin(
/config\/env\.js/,
'./config/env.production.js'
)
]
};
В данном примере любой импорт config/env.js будет
заменён на config/env.production.js.
Вместо строки может использоваться функция, позволяющая динамически определять целевой модуль:
new webpack.NormalModuleReplacementPlugin(
/config\/env\.js/,
(resource) => {
resource.request = resource.request.replace(
'env.js',
'env.staging.js'
);
}
);
Объект resource содержит информацию о текущем запросе
модуля, включая путь, контекст и метаданные. Изменение
resource.request влияет на финальный резолв.
Одним из классических сценариев является подмена конфигураций окружений без изменения исходного кода:
new webpack.NormalModuleReplacementPlugin(
/env\.js$/,
(resource) => {
const env = process.env.NODE_ENV;
if (env === 'production') {
resource.request = resource.request.replace('env.js', 'env.prod.js');
}
if (env === 'development') {
resource.request = resource.request.replace('env.js', 'env.dev.js');
}
}
);
Иногда требуется заменить одну библиотеку другой без изменения импортов:
new webpack.NormalModuleReplacementPlugin(
/legacy-library/,
'modern-library'
);
Это используется при постепенной миграции крупных кодовых баз.
NormalModuleReplacementPlugin работает только на уровне полного
совпадения модуля или пути. Он не анализирует внутреннюю структуру кода
и не влияет на динамические контексты, создаваемые через
require.context.
Кроме того, чрезмерное использование подмен может усложнить трассировку зависимостей, так как фактический импорт отличается от исходного кода.
ContextReplacementPlugin предназначен для управления контекстными зависимостями, которые возникают при использовании динамических выражений импорта. Webpack в таких случаях пытается включить все потенциально подходящие файлы, что может приводить к разрастанию бандла.
Контекст возникает при использовании выражений вида:
require('./locales/' + lang + '.json');
Webpack не может статически определить конкретный файл и создаёт
контекст, включающий все файлы в папке locales.
Если в папке находятся десятки или сотни файлов, Webpack добавляет их все в сборку. Это приводит к:
ContextReplacementPlugin позволяет ограничить этот контекст.
Плагин перехватывает создание контекстного модуля и заменяет его параметры: путь, регулярное выражение поиска и дополнительные ограничения.
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ContextReplacementPlugin(
/locales/,
false,
{}
)
]
};
Первый аргумент — регулярное выражение, определяющее область контекста. Второй — новый контекст или ограничение. Третий — дополнительные параметры.
Часто используется фильтрация доступных файлов:
new webpack.ContextReplacementPlugin(
/locales/,
(context) => {
context.regExp = /^\.\/(en|ru)\.json$/;
}
);
В этом случае в бандл попадут только en.json и
ru.json.
ContextReplacementPlugin часто применяется в Angular-проектах для контроля загрузки локализаций:
new webpack.ContextReplacementPlugin(
/angular[\\/]core[\\/]fesm/,
path.resolve(__dirname, 'src'),
{}
);
Здесь ограничивается контекст Angular, чтобы исключить лишние локали и вспомогательные файлы.
Объект контекста может быть изменён:
new webpack.ContextReplacementPlugin(
/components/,
(data) => {
data.request = './src/components';
data.regExp = /^\.\/Button/;
}
);
Это позволяет включить только компонент Button,
игнорируя остальные.
NormalModuleReplacementPlugin и ContextReplacementPlugin решают разные задачи на разных уровнях системы модулей.
Подмена одного файла:
NormalModuleReplacementPlugin -> ./config/dev.js → ./config/prod.js
Ограничение множества файлов:
ContextReplacementPlugin -> ./locales/* → только en.json и ru.json
Оба плагина взаимодействуют с системой Tapable, на которой построен Webpack. Они подписываются на хуки компиляции и резолва модулей.
NormalModuleReplacementPlugin вмешивается между шагами 1 и 2, изменяя запрос до создания модуля.
ContextReplacementPlugin влияет на шаг 2, изменяя параметры контекстного модуля.
Если регулярное выражение слишком широкое:
/.*config.*/
можно случайно заменить лишние модули, включая сторонние зависимости.
При слишком агрессивной фильтрации ContextReplacementPlugin может удалить необходимые модули, что приведёт к runtime-ошибкам вида:
Cannot find module
Из-за динамической природы подмен итоговый код может существенно отличаться от исходного, что затрудняет поиск причин поведения приложения.
Dynamic import усиливает важность ContextReplacementPlugin:
import(`./modules/${name}.js`);
Webpack формирует контекст и включает все файлы в директории. ContextReplacementPlugin позволяет сузить этот набор.
Ограничение локалей:
new webpack.ContextReplacementPlugin(
/i18n/,
(data) => {
data.regExp = /^\.\/(en|ru|kz)\.json$/;
}
);
Отключение модулей фич:
new webpack.NormalModuleReplacementPlugin(
/feature-flags/,
'./feature-flags.prod.js'
);
Постепенное переключение зависимостей:
new webpack.NormalModuleReplacementPlugin(
/jquery/,
'cash-dom'
);
Оба плагина косвенно влияют на tree shaking, поскольку изменяют сам граф зависимостей до его оптимизации. Удаление ненужных модулей на уровне контекста повышает эффективность последующего удаления мёртвого кода.
NormalModuleReplacementPlugin и ContextReplacementPlugin используются на этапе конфигурации Webpack как инструменты архитектурного управления зависимостями. Они позволяют:
Их применение требует понимания структуры графа модулей и механики резолва, поскольку любые изменения влияют на итоговую структуру приложения на уровне компиляции.