NeuroAgent

Webpack MiniCssExtractPlugin: Avoid Small JS Chunks with Dynamic CSS Imports

Learn how to configure webpack with MiniCssExtractPlugin to prevent generating unnecessary JavaScript chunks when using dynamic CSS imports. Optimize your build process with FixStyleOnlyEntriesPlugin and proper splitChunks configuration.

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:

javascript
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:

javascript
"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.

NeuroAgent

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

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:

javascript
"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:

javascript
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:

  1. Analyzing the generated chunks after compilation
  2. Identifying chunks that contain only CSS (no JavaScript code)
  3. Merging these chunks with their parent entry points
  4. 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:

javascript
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 chunks
  • maxInitialRequests: 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:

javascript
// 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:

javascript
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:

javascript
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

javascript
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

javascript
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

  1. Style Only Entries with Webpack and Sass - MediaRon LLC
  2. Webpack Performance Optimization - Manohar Batra
  3. Webpack Performance Tuning - DEV Community
  4. Name CSS Split Chunks using MiniCssExtractPlugin - Curious Programmer
  5. 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:

  1. Use FixStyleOnlyEntriesPlugin - This is the primary solution that prevents CSS-only entries from creating separate JavaScript chunks
  2. Optimize splitChunks configuration - Set appropriate size thresholds and cache groups to control chunk generation
  3. Consider alternative approaches - Static imports with conditional loading or webpack magic comments can provide more control
  4. 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.