splitChunks can create initial chunks that are empty after CSS extraction

Bug report

What is the current behavior?

The splitChunks feature (using chunks: 'all') doesn’t take into account CSS extraction (via mini-css-extract-plugin) when deciding whether to create a new inital chunk. A new chunk can end up being created that is empty (other than the webpack functions) and under the default 30KB splitChunks.minSize threshold so shouldn’t have been created.

eg:
dist/vendors~pageA~pageB.js

For webpack build log output see:
https://github.com/edmorley/testcase-webpack-splitchunks-css#actual

If the current behavior is a bug, please provide the steps to reproduce.

  1. git clone https://github.com/edmorley/testcase-webpack-splitchunks-css.git
  2. yarn install
  3. yarn build

What is the expected behavior?

There is no dist/vendors~pageA~pageB.js chunk generated, since after the CSS is extracted, there should be no common JS code between the two pages.

Other relevant information:
webpack version: 4.8.3
Node.js version: 10.1.0
Operating System: Windows 10
Additional tools: mini-css-extract-plugin@0.4.0

Author: Fantashit

11 thoughts on “splitChunks can create initial chunks that are empty after CSS extraction

  1. Another issue that this causes, is that the empty (plus also “not quite empty, but still less than the 30KB minimum size”) chunks use up the maxInitialRequests allowance – forcing some of the larger modules into the entrypoint chunks where they are duplicated, meaning that the total built output size is greater.

    This has the knock on effect of slower builds , since there is more to minify/source-map. (In one of my projects raising maxInitialRequests higher than the default of 3 reduces cold cache build times by 40%% and warm by 30-35%%.)

  2. This is blocking me to upgrade to webpack 4, but it is planned for webpack 5. I guess I will stop trying to upgrade until 5 comes out.

  3. Right now I’m using this “workaround”:

    class MiniCssExtractPluginCleanup {
        apply(compiler) {
            compiler.hooks.emit.tapAsync("MiniCssExtractPluginCleanup", (compilation, callback) => {
                Object.keys(compilation.assets)
                    .filter(asset => {
                        return ["*/scss/**/*.js", "*/scss/**/*.js.map"].some(pattern => {
                            return minimatch(asset, pattern);
                        });
                    })
                    .forEach(asset => {
                        delete compilation.assets[asset];
                    });
    
                callback();
            });
        }
    }
    

    It’s very specific for my use case and has things hardcoded and I even have just put it directly in the webpack.config.js file (so not published on npm).

    So it would be awesome if this could be fixed at webpack level.

  4. @danechitoaie ‘s “workaround” worked for me, but it does unnecessary looping, incurs a dependency in minimatch, and isn’t very flexible as mentioned. I modified to this which passes in a RegExp to be reused for what to delete, as well as not doing extra loops via higher-order functions:

    class MiniCssExtractPluginCleanup {
      constructor(deleteWhere = /\.js(\.map)?$/) {
        this.shouldDelete = new RegExp(deleteWhere)
      }
      apply(compiler) {
        compiler.hooks.emit.tapAsync("MiniCssExtractPluginCleanup", (compilation, callback) => {
          Object.keys(compilation.assets).forEach((asset) => {
            if (this.shouldDelete.test(asset)) {
              delete compilation.assets[asset]
            }
          })
          callback()
        })
      }
    }

    Which then in their case could be used by passing in the RegExp

    module.exports = {
       ...,
       plugins: [
         new MiniCssExtractPluginCleanUp(),
       ],
      ...,
    }
  5. Also remember you can just run console command to delete / modify this files after webpack done his job.

    // package.json
    "scripts": {
        "clean-css": "rm ./build/normalize.bundle.js",
        "build": "webpack --config webpack.dev.js && npm run clean-css",
    }
    

    Can be useful: Pre and Post hooks for npm scripting

  6. Partial solved in webpack@5:

    {
      optimization: {
        splitChunks: {
          cacheGroups: {
            styles: {
              type: 'css/mini-extract',
              chunks: 'all',
              // If you need this uncomment
              // enforce: true,
            },
          },
        },
      },
    }

Comments are closed.