Как настроить webpack с MiniCssExtractPlugin, чтобы избежать генерации небольших JavaScript-чанков при использовании динамических импортов CSS?
При использовании MiniCssExtractPlugin в webpack для извлечения стилей в отдельные статические файлы, конфигурация работает хорошо. Однако при реализации динамических импортов для CSS webpack генерирует два файла для каждого импорта: один CSS-файл и один JavaScript-файл.
Например, при следующем коде:
if (isMobile) {
import('./mobile.css')
}
if (isDesktop) {
import('./desktop.css')
}
Webpack генерирует четыре файла:
- mobile.js
- mobile.css
- desktop.js
- desktop.css
И mobile.js, и desktop.js - это небольшие модули, которые вызывают только одну функцию:
"use strict";
(self["webpackChunksportpursuit"] = self["webpackChunksportpursuit"] || []).push([[9996],{
/***/ 34568:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
// extracted by mini-css-extract-plugin
/***/ })
}]);
По соображениям производительности я хочу избежать создания этих дополнительных JavaScript-файлов. Цель - сохранить эту логику внутри родительского модуля, который импортирует стили.
Чтобы избежать создания небольших JavaScript чанков при использовании динамических импортов CSS с MiniCssExtractPlugin, необходимо использовать FixStyleOnlyEntriesPlugin и оптимизировать конфигурацию webpack для объединения записей, содержащих только стили, с их родительскими модулями. Это предотвращает создание webpack отдельных JavaScript файлов, которые содержат только одну функцию для загрузки CSS.
Содержание
- Понимание проблемы
- Решение с FixStyleOnlyEntriesPlugin
- Оптимизация конфигурации splitChunks
- Альтернативные подходы
- Полные примеры конфигурации
Понимание проблемы
При использовании динамических импортов CSS, таких как import('./mobile.css') с MiniCssExtractPlugin, webpack рассматривает каждый динамический импорт как отдельную точку входа. Это создает как CSS файлы, так и JavaScript чанки. Файлы JavaScript содержат минимальный код, который по сути просто вызывает функцию загрузки CSS:
"use strict";
(self["webpackChunksportpursuit"] = self["webpackChunksportpursuit"] || []).push([[9996],{
/***/ 34568:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
// extracted by mini-css-extract-plugin
/***/ })
}]);
Эти небольшие JavaScript чанки неэффективны, потому что:
- Они создают ненужные HTTP-запросы
- Они добавляют минимальные накладные расходы на ваше приложение
- Они не предоставляют никакой реальной функциональности, кроме загрузки CSS
Согласно лучшим практикам webpack, следует избегать создания небольших чанков, которые не служат осмысленной цели в архитектуре вашего приложения.
Решение с FixStyleOnlyEntriesPlugin
Наиболее эффективным решением является использование плагина webpack-fix-style-only-entries, который специально разработан для обработки именно этого сценария. Этот плагин определяет, когда чанк содержит только CSS (без JavaScript), и объединяет его с родительским чанком вместо создания отдельного JavaScript файла.
Вот как его реализовать:
const path = require('path');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ... другая конфигурация webpack
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
new FixStyleOnlyEntriesPlugin(), // Это предотвращает создание небольших JS чанков для записей, содержащих только CSS
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
],
},
};
FixStyleOnlyEntriesPlugin работает следующим образом:
- Анализирует сгенерированные чанки после компиляции
- Определяет чанки, которые содержат только CSS (без JavaScript кода)
- Объединяет эти чанки с их родительскими точками входа
- Удаляет отдельные файлы JavaScript чанков
Это рекомендуемый подход согласно лучшим практикам конфигурации webpack для масштабных приложений, так как он поддерживает чистые сборки, избегая при этом ненужных небольших чанков.
Оптимизация конфигурации splitChunks
В дополнение к использованию FixStyleOnlyEntriesPlugin, вы можете оптимизировать конфигурацию splitChunks webpack для дальнейшего контроля над созданием чанков:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // Создавать чанки только размером более 30 КБ
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
default: false,
vendors: false,
framework: {
name: 'framework',
chunks: 'all',
test: /[\\/]node_modules[\\/]/,
priority: 40,
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 20,
},
},
},
},
};
Ключевые параметры, которые следует учитывать:
minSize: Установите минимальный порог размера, чтобы избежать создания очень маленьких чанковmaxInitialRequests: Ограничьте количество начальных чанков, чтобы предотвратить слишком много параллельных запросов- Отключите группы кэша по умолчанию, когда вам нужен более точный контроль над созданием чанков
Альтернативные подходы
Статические импорты с условной загрузкой
Если вы используете CSS модули или хотите больше контроля, рассмотрите статические импорты с условной загрузкой:
// Вместо динамических импортов:
if (isMobile) {
import('./mobile.css')
}
// Используйте статические импорты с условным применением:
import mobileCSS from './mobile.css';
import desktopCSS from './desktop.css';
// Применяйте CSS условно в вашем компоненте
if (isMobile) {
document.head.appendChild(mobileCSS);
} else {
document.head.appendChild(desktopCSS);
}
Использование магических комментариев webpack
Для большего контроля над динамическими импортами используйте магические комментарии webpack:
if (isMobile) {
import(/* webpackMode: "eager" */ './mobile.css')
}
Параметр webpackMode: "eager" указывает webpack загружать CSS немедленно вместе с родительским чанком, а не создавать отдельный асинхронный чанк.
Встраивание CSS с динамической загрузкой
Еще одна альтернатива - использование подходов CSS-in-JS или встраивание CSS контента:
async function loadDynamicCSS() {
if (isMobile) {
const css = await import('./mobile.css');
const style = document.createElement('style');
style.textContent = css.default;
document.head.appendChild(style);
}
}
Полные примеры конфигурации
Конфигурация для продакшена с оптимизацией
const path = require('path');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
default: false,
vendors: false,
framework: {
name: 'framework',
chunks: 'all',
test: /[\\/]node_modules[\\/]/,
priority: 40,
},
},
},
runtimeChunk: 'single',
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
new FixStyleOnlyEntriesPlugin(),
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
},
},
'postcss-loader',
],
},
],
},
};
Конфигурация для разработки
const path = require('path');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[name].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new FixStyleOnlyEntriesPlugin(),
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
],
},
devtool: 'source-map',
};
Источники
- Style Only Entries with Webpack and Sass - MediaRon LLC
- Webpack Performance Optimization - Manohar Batra
- Webpack Performance Tuning - DEV Community
- Name CSS Split Chunks using MiniCssExtractPlugin - Curious Programmer
- Webpack Configuration Best Practices for Large-Scale Applications - Moldstud
Заключение
Чтобы избежать создания небольших JavaScript чанков при использовании динамических импортов CSS с MiniCssExtractPlugin, реализуйте эти ключевые стратегии:
- Используйте FixStyleOnlyEntriesPlugin - Это основное решение, которое предотвращает создание отдельных JavaScript чанков для записей, содержащих только CSS
- Оптимизируйте конфигурацию splitChunks - Установите соответствующие пороги размера и группы кэша для контроля создания чанков
- Рассмотрите альтернативные подходы - Статические импорты с условной загрузкой или магические комментарии webpack могут обеспечить больший контроль
- Тестируйте различные конфигурации - Экспериментируйте с разными настройками webpackMode и стратегиями чанков для вашего конкретного случая использования
Реализуя эти оптимизации, вы сохраните преимущества разделения кода и извлечения CSS, избегая при этом накладных расходов от ненужных небольших JavaScript чанков. Этот подход соответствует лучшим практикам webpack для создания эффективных, масштабируемых приложений.