Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

splitChunks can create initial chunks that are empty after CSS extraction #7300

Closed
edmorley opened this issue May 14, 2018 · 52 comments
Closed

Comments

@edmorley
Copy link
Contributor

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

@alexander-akait
Copy link
Member

@edmorley Maybe related webpack-contrib/mini-css-extract-plugin#85?

@edmorley
Copy link
Contributor Author

Hi! I saw that issue before filing this, however in that issue a custom cacheGroups definition was being used to move all CSS to one chunk - whereas this case is with the default cacheGroups setting. The symptoms do seem similar, so it's not implausible that it's the same root cause - but I didn't want to assume and pile on there.

@sokra sokra added this to the webpack 4.x milestone May 15, 2018
@sokra
Copy link
Member

sokra commented May 15, 2018

Yep that's true

@edmorley
Copy link
Contributor Author

edmorley commented Jun 8, 2018

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%.)

@paynecodes
Copy link

paynecodes commented Jun 21, 2018

@sokra Is there any workaround for this issue you can think of? I'm simply trying to split out all vendor css, but this is creating an "empty" (file contents: (window.webpackJsonp=window.webpackJsonp||[]).push([[0],[]]);) js file. Any suggestions?

@azamat-sharapov
Copy link

@jpdesigndev try prepending those files' contents into you main entry manually.

@louisscruz
Copy link

Any news on this? I think this is the final blocker for our upgrade to Webpack 4.

@kasperpeulen
Copy link

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.

@edmorley
Copy link
Contributor Author

The milestone for this used to be webpack 4.x, but has recently been changed to webpack 5. Was this due to the fix for this being seen as a breaking change, or because of timeframe/resources required to fix?

@alexander-akait
Copy link
Member

Yes, it is breaking change

@sokra sokra modified the milestones: webpack 5, webpack 4.x Jun 22, 2018
@sokra
Copy link
Member

sokra commented Jun 22, 2018

hmm... maybe there is a way we could add it for webpack 4 too

@evenstensberg
Copy link
Member

If this needs help, I'm happy to help out

@Igloczek
Copy link

Igloczek commented Jul 4, 2018

Seems like related to webpack-contrib/mini-css-extract-plugin#151

@alexander-akait
Copy link
Member

/cc @sokra will be great solve this for webpack@4, because a lot of people have this problem

@danechitoaie
Copy link

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.

@maapteh
Copy link

maapteh commented Aug 16, 2018

@sokra will this still be fixed in 4? We are migrating to 4 currently and everything works except this "empty" file.

Or wait for 5 with css-webpack-plugin ? Awesome work by the way, i only had problems with the optimise part!

@toastal
Copy link

toastal commented Aug 17, 2018

@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(),
   ],
  ...,
}

@Kajeczko
Copy link

Kajeczko commented Aug 20, 2018

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

@usebaz
Copy link

usebaz commented Aug 27, 2018

@Kajeczko in some cases this file can have code that run webpack. Example:

⇒  cat build/_js/route.styles.js 
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["styles"],[]]);

@Dwood15
Copy link

Dwood15 commented Aug 27, 2018

@usebaz that kind of code isn't necessary to use the css files, and are safe to delete.

@alexander-akait
Copy link
Member

Partial solved in webpack@5:

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

@alexander-akait
Copy link
Member

#11585 should fix it, I will put couple tests in mini-css-extract-plugin to ensure all works fine

@alexander-akait
Copy link
Member

Done webpack-contrib/mini-css-extract-plugin#599

@alexander-akait alexander-akait moved this from Maybe to Done in webpack 5 Oct 7, 2020
@martinherweg
Copy link

@evilebottnawi I've tried with the following packages
webpack: 5.0.0
mini-css-extract-plugin: 1.0.0
css-loader: 4.3.0

and this config

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = () => {
  return {
    entry: {
      test: './src/test.css',
    },
    mode: 'production',
    optimization: {
      removeEmptyChunks: true,
      splitChunks: {
        cacheGroups: {
          styles: {
            type: 'css/mini-extract',
            chunks: 'all',
          },
        },
      },
    },
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                esModules: false,
              },
            },
            {
              loader: 'css-loader',
            },
          ],
        },
      ],
    },
    plugins: [new MiniCssExtractPlugin()],
  };
};

but it still outputs a dist/test.css and dist/test.js with the latter being empty, any hint what might be wrong here?

@alexander-akait
Copy link
Member

@martinherweg Please create reproducible test repo, I think you still on old webpack

@martinherweg
Copy link

@evilebottnawi https://github.com/martinherweg/webpack-css-chunks here I've created a very small test repo.

@alexander-akait
Copy link
Member

@martinherweg to be honestly it is not related to this issue, we create empty js because you need js entrypoint, but because you don't have js it is just empty, it's a bit like this problem, but it's a different problem, can you open a new issue about this?

@martinherweg
Copy link

@evilebottnawi just to confirm my understanding. For which cases is your fix where it doesn't generate an empty JS file?

I've added an additional entry point with a JS file, still it generates an empty JS file for the CSS only entry.

I understand it's not related to that issue, I just read various threads about this and read your comment here that it's fixed.

Do you think it's webpack or mini-css-extract-plugin related?

Thanks.

@alexander-akait
Copy link
Member

@martinherweg

@evilebottnawi just to confirm my understanding. For which cases is your fix where it doesn't generate an empty JS file?

For all cases, except non js entry points

Do you think it's webpack or mini-css-extract-plugin related?

webpack

@Gnodesign
Copy link

So there still isn't a fix for this?

@alexander-akait
Copy link
Member

Partial fixed

@MichaelDurfey
Copy link

I created a plugin to fix this for us and hold us over until webpack 5. Works fine after the webpack 5 upgrade, but this plugin did the job until then: https://github.com/michaeldurfey/fix-style-emits-webpack-plugin Just concats the empty js files onto the main entrypoint js.

@meglio
Copy link

meglio commented Apr 5, 2021

Still not fixed for me when using webpack 5:

cacheGroups: {
                    styles: {
                        name: 'styles',
                        test: /\.(sa|sc|c)ss$/,
                        //type: 'css/mini-extract',
                        chunks: 'all',
                        enforce: true,
                        minSize: 0,
                        minChunks: 1,
                        priority: 20
                    },
                    vendor: {
                        test: /node_modules/,
                        chunks: "initial",
                        name: "vendor",
                        priority: 10,
                        enforce: true
                    },
                    commons: {
                        chunks: "initial",
                        name: "commons",
                        minChunks: 1,
                        enforce: true,
                        maxInitialRequests: 4,
                        minSize: 0
                    },
                }
}

Produces the following js file:

image

@alexander-akait
Copy link
Member

@meglio Not all chunks can be dropped, please create reproducible test repo, I will look

@meglio
Copy link

meglio commented Apr 5, 2021

@alexander-akait , I've been playing with various options and made the following discovery:

Using test: /\.(sa|sc|c)ss$/ in the chunk configuration does emit the js file besides the css file.

Removing that and using type: 'css/mini-extract' instead stops emitting the js file and only emits the css file.

Does this make any sense, and is there an explanation of this behaviour?

@alexander-akait
Copy link
Member

@meglio

Does this make any sense, and is there an explanation of this behaviour?

type was implement for this, test doesn't know which modules should be dropped, it is right solution, can you help update docs?

@meglio
Copy link

meglio commented Apr 5, 2021

Is there any way to contribute to docs without running a full local developer copy of the webpack website?

@alexander-akait
Copy link
Member

You can send improve here https://github.com/webpack-contrib/mini-css-extract-plugin

@meglio
Copy link

meglio commented Apr 5, 2021

Do you suggest adding a note in this section?

@phil-lgr
Copy link

phil-lgr commented Dec 9, 2021

Thanks @toastal for your minimal plugin, it does the job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests