How to configure webpack with MiniCssExtractPlugin to avoid generating small JavaScript chunks when using dynamic CSS imports?
When using MiniCssExtractPlugin in webpack to extract styles into separate static files, the configuration works well. However, when implementing dynamic imports for CSS, webpack generates two files for each import: one CSS file and one JavaScript file.
For example, with the following code:
if (isMobile) {
import('./mobile.css')
}
if (isDesktop) {
import('./desktop.css')
}
Webpack generates four files:
- mobile.js
- mobile.css
- desktop.js
- desktop.css
Both mobile.js and desktop.js are small modules that only invoke a single function:
"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
/***/ })
}]);
For performance reasons, I want to avoid creating these extra JavaScript files. The goal is to keep this logic within the parent module that imports the styles.
To avoid generating small JavaScript chunks when using dynamic CSS imports with MiniCssExtractPlugin, you need to use the FixStyleOnlyEntriesPlugin and optimize your webpack configuration to merge style-only entries with their parent modules. This prevents webpack from creating separate JavaScript files that only contain a single function call to load CSS.
Contents
- Understanding the Problem
- Solution with FixStyleOnlyEntriesPlugin
- Optimizing splitChunks Configuration
- Alternative Approaches
- Complete Configuration Examples
Understanding the Problem
When you use dynamic CSS imports like import('./mobile.css') with MiniCssExtractPlugin, webpack treats each dynamic import as a separate entry point. This creates both CSS files and JavaScript chunk files. The JavaScript files contain minimal code that essentially just calls the CSS loading function:
"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
/***/ })
}]);
These small JavaScript chunks are inefficient because:
- They create unnecessary HTTP requests
- They add minimal overhead to your application
- They don’t provide any actual functionality beyond loading CSS
According to webpack best practices, you should avoid creating small chunks that don’t serve a meaningful purpose in your application’s architecture.
Solution with FixStyleOnlyEntriesPlugin
The most effective solution is to use the webpack-fix-style-only-entries plugin, which is specifically designed to handle this exact scenario. This plugin detects when a chunk contains only CSS (no JavaScript) and merges it with its parent chunk instead of creating a separate JavaScript file.
Here’s how to implement it:
const path = require('path');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ... other webpack config
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
new FixStyleOnlyEntriesPlugin(), // This prevents small JS chunks for CSS-only entries
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
],
},
};
The FixStyleOnlyEntriesPlugin works by:
- Analyzing the generated chunks after compilation
- Identifying chunks that contain only CSS (no JavaScript code)
- Merging these chunks with their parent entry points
- Removing the separate JavaScript chunk files
This is the recommended approach according to webpack configuration best practices for large-scale applications, as it maintains clean builds while avoiding unnecessary small chunks.
Optimizing splitChunks Configuration
In addition to using FixStyleOnlyEntriesPlugin, you can optimize your webpack’s splitChunks configuration to further control chunk generation:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // Only create chunks larger than 30KB
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,
},
},
},
},
};
Key settings to consider:
minSize: Set a minimum size threshold to avoid creating very small chunksmaxInitialRequests: Limit the number of initial chunks to prevent too many parallel requests- Disable default cache groups when you want more control over chunk creation
Alternative Approaches
Static Imports with Conditional Loading
If you’re using CSS modules or want more control, consider static imports with conditional loading:
// Instead of dynamic imports:
if (isMobile) {
import('./mobile.css')
}
// Use static imports with conditional application:
import mobileCSS from './mobile.css';
import desktopCSS from './desktop.css';
// Apply CSS conditionally in your component
if (isMobile) {
document.head.appendChild(mobileCSS);
} else {
document.head.appendChild(desktopCSS);
}
Using webpack’s Magic Comments
For more control over dynamic imports, use webpack’s magic comments:
if (isMobile) {
import(/* webpackMode: "eager" */ './mobile.css')
}
The webpackMode: "eager" option tells webpack to load the CSS immediately with the parent chunk rather than creating a separate async chunk.
Inline CSS with Dynamic Loading
Another alternative is to use CSS-in-JS approaches or inline the CSS content:
async function loadDynamicCSS() {
if (isMobile) {
const css = await import('./mobile.css');
const style = document.createElement('style');
style.textContent = css.default;
document.head.appendChild(style);
}
}
Complete Configuration Examples
Production Configuration with Optimization
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',
],
},
],
},
};
Development Configuration
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',
};
Sources
- 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
Conclusion
To avoid generating small JavaScript chunks when using dynamic CSS imports with MiniCssExtractPlugin, implement these key strategies:
- Use FixStyleOnlyEntriesPlugin - This is the primary solution that prevents CSS-only entries from creating separate JavaScript chunks
- Optimize splitChunks configuration - Set appropriate size thresholds and cache groups to control chunk generation
- Consider alternative approaches - Static imports with conditional loading or webpack magic comments can provide more control
- Test different configurations - Experiment with different webpackMode settings and chunk strategies for your specific use case
By implementing these optimizations, you’ll maintain the benefits of code splitting and CSS extraction while avoiding the performance overhead of unnecessary small JavaScript chunks. This approach aligns with webpack best practices for building efficient, scalable applications.