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

Disable Async Chunking #41

Closed
dtothefp opened this issue Mar 16, 2018 · 19 comments
Closed

Disable Async Chunking #41

dtothefp opened this issue Mar 16, 2018 · 19 comments

Comments

@dtothefp
Copy link

This may be an anti-pattern to what mini-css-extract-plugin is trying to accomplish but I'm migrating from Webpack v2 to Webpack v4 in a large React application with many async routes containing CSS modules and extract-text-plugin is not working correctly. mini-css-extract-plugin works well but async chunking causes some page load animations, etc. to break because previously all of the CSS was loaded in a single bundle.

I'm wondering if there is an option for the plugin that I can disable CSS chunking until my team has time to address considerations associated with CSS chunking?

@garygreen
Copy link

@dtothefp maybe put up a basic example repository? Hard to advise without seeing the entry points, what splitChunks you've tried etc

@dtothefp
Copy link
Author

dtothefp commented Mar 16, 2018

Alright, I put this together https://github.com/dtothefp/webpack-4-chunks-demo. It is basically a copy of https://github.com/dtothefp/webpack-4-chunks-demo but I updated to Webpack 4 and mini-css-extract-plugin.

On branch mini-css-extract-plugin run yarn start:prod. You will see that 3 JS bundles and 2 CSS chunks are created.

screen shot 2018-03-16 at 6 14 35 pm

screen shot 2018-03-16 at 6 14 22 pm

Now if you go to the Webpack v3 branch yarn start:prod only 2 JS bundle is created with 1 CSS chunk. This uses extract-text-webpack-plugin without CommonChunksPlugin.

screen shot 2018-03-16 at 6 06 50 pm

Therefore, I'd like to be able to create only 1 single CSS bundle containing all the CSS for my app. In this example SCSS is imported in the src/index.js and in the routes/Home/Home.js.

In another repo I've tried messing around with the optimization.splitChunks options as suggested here https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693 but I haven't figured it out, or even discovered if this will work for my CSS use case.

    optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
            name: 'commons',
            test: /\.css$/,
            chunks: 'all',
            minChunks: 2,
            enforce: true
          }
        }
      }
    },

@garygreen
Copy link

That's a lot of information. Might be worth trimming down your example to the bare minimum (and maybe even removing postcss / sass etc and just work with a basic css, entries and import examples).

In the other thread you said you use ensure but in the example your just importing it as part of build chain, so that isn't async?

Anyway, looks like you just want to turn off the default chunking, should be able to just do:

optimization: {
      splitChunks: {
        cacheGroups: {
          default: false
          }
        }
      }
    },

@dtothefp
Copy link
Author

dtothefp commented Mar 17, 2018

hey @garygreen sorry if the example was too in depth...didn't want to spend time setting up my own repo. the essentials are the entry point is a synchronous chunk of css https://github.com/dtothefp/webpack-4-chunks-demo/blob/mini-css-extract-plugin/src/index.js#L8 and then there is an async chunk in the route using a dynamic import (my previous comment you answered I said I was using require.ensure but this one I used dynamic import to stick with newer patterns) https://github.com/dtothefp/webpack-4-chunks-demo/blob/mini-css-extract-plugin/src/routes/Home/index.js#L5 => styles imported here https://github.com/dtothefp/webpack-4-chunks-demo/blob/mini-css-extract-plugin/src/routes/Home/Home.js#L5. I can work to put together using a more simple example without postcss / scss and see if that makes any difference.

my problem is the documentation for splitChunks is tough to understand and I'm not seeing any differences regardless of the configuration I pass it. I understand under the hood it configures the SplitChunksPlugin so I may start to dig in there. I used your configuration above and it had not effect, still ended up with the same amount of css / js files. Like I said my ultimate goal is to end up with a single CSS file but still retain the chunked JS files

    home.2d024e5e30d0dc0d9fdd.js
    home.css
    index.html
    main.css
    main.js
    vendors~home.1cec51d55d67c7af392f.js
    vendors~home.css

@pshurygin
Copy link

You can try something like this:

  optimization: {
    splitChunks: {
      chunks: 'all',//split both async chunks and initial chunks(entrypoints)
      cacheGroups: {
        default: false,//disable default 'commons' chunk behavior
        vendors: false, //disable vendor splitting(not sure if you want it)
        styles: {
          name: 'styles',
          test: /\.s?css$/,
          minChunks: 1
        }
      }
    }
  }

This will give you a single css file, BUT it will be an asynchronously loaded file(and html webpack plugin wont inject it into your index.html via link tag). That is because it contains styles imported from async chunks. I am not sure if this will work for your case and if there is any kind of workaround to extract all styles as a sync chunk.

@dtothefp
Copy link
Author

dtothefp commented Mar 17, 2018

@pshurygin this is great and so close to exactly what I want. The only problem is the asynchronously loaded file, not because of the html plugin (generally generate stats and create the link tag in HTML myself) but just because I want to load the link in the head and not make an extra call for that.

Do you know if there is anyway to change this behavior?

Update

I was actually incorrect. The HTML plugin is actually creating a <link> for the styles, but there is also an extra styles.<some_hash>.js file created that it puts in a <script> tag above the main.js. This styles.js doesn't do much as I can tell just creates an object inside the webpackJsonp queue with methods only containing comments:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{

/***/ 167:
/***/ (function(module, exports, __webpack_require__) {

// extracted by mini-css-extract-plugin

/***/ }),

If this file is not loaded above main.js the chunked out home.<some_hash>.js won't load. I'm assuming this has something to do with how main.js is checking in the queue for async chunks to load? Anyhow, the results looks something like

<html lang="en">
  <head>
  <link href="styles.css" rel="stylesheet"></head>
  <body>
    <div id="root" style="height: 100%"></div>
    <script type="text/javascript" src="styles.faba426d1146447cb52a.js"></script>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

screen shot 2018-03-17 at 10 48 21 am

In my other app using require.ensure I haven't gotten even this to work even with ensure: true and can try to put together a smaller use case example

@pshurygin
Copy link

You can also inline styles.js into html with something like this plugin https://github.com/numical/script-ext-html-webpack-plugin to avoid extra server call.

@dtothefp
Copy link
Author

@pshurygin I realized from your configuration above it results in the wrong order of css in the bundle.

For example there is css import in the following order:

  • the JS entrypoint
// src/index.js
import './index.scss'
// src/index.scss
@import '~normalize.css/normalize';
@import '~bulma/bulma';
  • the async component inside a dynamic import
// src/routes/Home/Home.js
import Spinner from 'react-spinkit'

import './style.scss'
// react-spinkit
  require('loaders.css');
  require('../css/base.css');
  require('../css/loaders-css.css');
  require('../css/fade-in.css');
  require('../css/chasing-dots.css');
  require('../css/circle.css');
  require('../css/cube-grid.css');
  require('../css/double-bounce.css');
  require('../css/folding-cube.css');
  require('../css/pulse.css');
  require('../css/rotating-plane.css');
  require('../css/three-bounce.css');
  require('../css/wandering-cubes.css');
  require('../css/wave.css');
  require('../css/wordpress.css');
// src/routes/Home/styles.css
@import "~bulma/sass/utilities/initial-variables";
@import "~bulma/sass/utilities/derived-variables";

.home {
  &__spinner {
    color: $light;
  }
}

with the configuration options

  optimization: {
    splitChunks: {
      chunks: 'all',//split both async chunks and initial chunks(entrypoints)
      cacheGroups: {
        default: false,//disable default 'commons' chunk behavior
        vendors: false, //disable vendor splitting(not sure if you want it)
        styles: {
          name: 'styles',
          test: /\.s?css$/,
          minChunks: 1
        }
      }
    }
  }

the resulting single bundle for CSS is in the order of

// react-spinkit and deps in correct order
// src/routes/Home/styles.css
// normalize.css/normalize
// bulma/bulma

whereas from the order of imports the desired bundle would be

// normalize.css/normalize
// bulma/bulma
// react-spinkit and deps in correct order
// src/routes/Home/styles.css

@pshurygin
Copy link

Do you have a single entrypoint? Couldnt that normalize styles come from it? You could try removing @import to test it or replace it with js import. If not, i guess it could be an issue with the plugin.

@es3154
Copy link

es3154 commented Mar 20, 2018

I also need a way of synchronous loading of CSS, put all the style package to a CSS file, because I need to do theme switching function, by switching different CSS theme of switching, but now the style of the pack out contains asynchronous CSS function, whether there is an option to disable the function of the asynchronous CSS? thank you

@bebraw
Copy link

bebraw commented Mar 26, 2018

I had a similar problem so I wrote a solution at #57. Tobias might do something more official at some point but this does the trick for now. Essentially I defined a small architecture that allows you to determine which CSS files to capture for an emitting stage. I think this bridges ETWP with MCEP while giving more flexibility.

@dtothefp
Copy link
Author

I've given up on this effort and resorted to adding an extra <link> tag per page that requests the CSS chunk that is associated with the async components loaded on that page. I'm isomorphically rendering my JS to HTML so this is necessary because an async CSS chunk will not be applied on initial page render, whereas my async JS component will be rendered into HTML. Therefore, CSS is loaded when async JS loads and results in initial broken layout before JS loads.

To figure out what async components load on the page and their associated CSS chunk names I use react-loadable https://github.com/jamiebuilds/react-loadable which provides a webpack and babel plugin creating as stats manifest and giving me an array of loaded async modules in my rendered isomorphic HTML.

This is great but when the async JS loads, it will load the async CSS in a <link> tag dynamically. Therefore, my async CSS is loaded twice, once in a synchronus <link> tag in the rendered HTML, and once when dynamically added by code inserted by mini-css-extract-plugin into the chunked JS https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/src/index.js#L190.

I'm wondering what best practice is around this and if there is a way to disable the async <link> tag insertion. Should an extra option be added to the plugin for this...and if so should I open a separate issue / PR?

@bwhitty
Copy link

bwhitty commented Mar 28, 2018

@dtothefp did you see #57 (comment)

If the <link> tags have the correct URL supposedly this plugin will not try to load it again, thus solving the duplicate loading for SSR'd apps.

@dtothefp
Copy link
Author

Looks like there is a blurb in the README right now on how to address this issue:

https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-all-css-in-a-single-file

I haven't tried it out though because I've moved to embracing chunking CSS and using react-loadable to discover the async chunk name / path and adding the <link> for in my HTML

@alexander-akait
Copy link
Member

@dtothefp can we close issue?

@a1mersnow
Copy link

a1mersnow commented May 7, 2018

What if I have multi entries and would like to lift up the component (async loaded via import()) scoped style into each entry chunk?
By now the behavior is, every async chunk will generate(or called extracted) a .css file...

@dtothefp
Copy link
Author

dtothefp commented May 7, 2018

@evilebottnawi I'm fine with closing...seems duplicated here #52 (comment). As I said previously I resorted to add chunks in extra <link> tag which is better for performance anyway but took me a while to figure out how to do this, and had to use a tool like react-loadable to discover which chunks were loaded on each page so I could create the <link>

@dtothefp dtothefp closed this as completed May 7, 2018
@oswaldofreitas
Copy link

If someone found an answer please post it here.

@cartok
Copy link

cartok commented Jun 4, 2021

In case you try to add lazy loaded modules / dynamic imports to the main bundle: webpack/webpack#11431

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

No branches or pull requests

10 participants