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

Control import order when importing with multiple rules #562

Closed
sallf opened this issue Aug 5, 2020 · 9 comments
Closed

Control import order when importing with multiple rules #562

sallf opened this issue Aug 5, 2020 · 9 comments

Comments

@sallf
Copy link

sallf commented Aug 5, 2020

  • Operating System: macOS 10.15.5 (Catalina)
  • Node Version: 12.16.2
  • NPM Version: 6.14.4
  • webpack Version: 4.41.5
  • mini-css-extract-plugin Version: 0.9.0

Expected Behavior / Situation

When using mini-css-extract-plugin to import both module css and non-module css, it would be great to be able to control the order of the css in the output file. I would prefer the output file to have all the css in my non-module css files followed by the css in all the esModule files.

Actual Behavior / Situation

The loader seems to always first load module css files, followed by non-module css files, no matter their order in the config file. This is an issue because my non-module css contain a bunch of boilerplate styles and vendors like Bootstrap and I would like to be able to override them easily.

In order to override them, I would have to add more specificity (or !important) to my module styles. For example:

/* css/general.css (non-css-module file) */
.label {
  color: red;
  underline: 2px solid red;
}

/* modules/component.css (css-module file) */
.override {
  color: blue;
}
<div class={`label ${styles.override}`}>
  I will be red
</div>

In this example, since general.css is always imported AFTER component.css, the resulting color will be red.

Here is the basic configuration:

...
{
  // Import all other css as global
  test: /\.css$/,
  exclude: /modules.*\.css$/,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
  ],
},
{
  // Import container and component index.css as CSS Modules
  test: /\.css$/,
  include: /modules.*\.css$/,
  loader: [
    MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader',
      options: {
        modules: {
          localIdentName: `${isDevelopment ? '[local]___' : ''}[hash:base64:5]`,
        },
      },
    },
  ],
},

Modification Proposal

I'm not sure I've accurately identified the problem (that css-modules are always imported first), but if it is correct, I think something like an importOrder option could work?

I believe this has been brought up in a number of places, though many comments seem to be related to fixing the import order in the css files themselves, which I don't think is the issue. Here are some of those that I think are related:

https://stackoverflow.com/questions/57487324/how-can-i-control-the-order-of-css-with-minicssextractplugin
#188

Note that as documented in issue 188, this is especially problematic when using style-loader in development, because it effectively reverses the import order and leads to a potential disparity in production.

@alexander-akait
Copy link
Member

alexander-akait commented Aug 6, 2020

Duplicate of #555 Duplicate #98 and other duplications, please use search before post the issue

@sallf
Copy link
Author

sallf commented Aug 6, 2020

@evilebottnawi thanks for taking a look. I could be wrong, but I don't think this is a duplicate. In my head, this is a different use case.

The two issues you referenced are about the import order of multiple css-modules compared to themselves. What I'm looking for is the ability to write to the output file the entire css-module chunk AFTER the non-css-module chunk.

Maybe I'm missing something but I couldn't find another similar issue that referenced a config file that shows two rules using MiniCssExtractPlugin.loader as I've shown above.

@alexander-akait
Copy link
Member

What I'm looking for is the ability to write to the output file the entire css-module chunk AFTER the non-css-module chunk.

Please clarify

@sallf
Copy link
Author

sallf commented Aug 6, 2020

@evilebottnawi I think the config file in the original post provides the most clarity, and to try and simplify further, in both of these webpack configs...

rules: [
  {
     // Test for CSS-MODULES
    // Use MiniCssExtractPlugin.loader
  },
  {
    // Test for NON-module css
    // Use MiniCssExtractPlugin.loader
  },
]

and (order reversed)

rules: [
  {
    // Test for NON-module css
    // Use MiniCssExtractPlugin.loader
  },
  {
    // Test for CSS-MODULES
    // Use MiniCssExtractPlugin.loader
  },
]

... the output css file will be ...

// All CSS-MODULES css

// ...

// All NON-module css

My hope is to have a way for non-module css to be above the css-modules, since the former is all boilerplate and vendors and should be easy to override with equal specificity css in the css-modules.

@alexander-akait
Copy link
Member

I still can't understand the problem? Why do you need this? Order of rules should not affect on something, you can event simplify you config with latest css-loader@4 release, just rename file.modules.css to file.module.css and module will be applied automatically https://github.com/webpack-contrib/css-loader#auto

@sallf
Copy link
Author

sallf commented Aug 6, 2020

@evilebottnawi Right, order of rules has no effect on output. So using auto, this could be simplified into a single rule.

rules: [
  {
    test: /\.css$/,
    loader: [
      MiniCssExtractPlugin.loader,
      {
        loader: 'css-loader',
        options: {
          modules: {
            auto: /modules.*\.css$/,
          },
        },
      },
    ],
  }
]

The issue is still the same. The output css file first has "module-css", followed by "non-module" css.

Why

I have a React app. In any component I use global (non-module) css along with component specific (module) css. If I have a component that looks like this:

import styles from './component.modules.css`;
...
<div className={`label ${styles.specificLabel}`}>
  Headline
</div>

And css that looks like this:

/* global.css */
.label {
  color: red;
  width: 100%;
}

/* component.modules.css */
.specificLabel {
  width: 50%;
}

The div would be 100% wide since the output file is essentially:

/* output.css */
.specificLabel {
  width: 50%;
}
.label {
  color: red;
  width: 100%;
}

Sorry if this is a weird use case but I feel like my setup is at least somewhat common - using both css-modules and non-module css in conjunction. It's common to occasionally want to override a boilerplate style with something that's specific to a component and being able to control which chunk is written out first would be advantageous.

@alexander-akait
Copy link
Member

Yes, you have invalid order of css modules, it should be fixed on app side, maybe you can create simple case with repo and I will provide examples how you can solve it

@sallf
Copy link
Author

sallf commented Aug 7, 2020

@evilebottnawi well that was exhausting, but I figured out what was going on in my setup and "order" is the right answer.

In my React SPA I import the full app into a root index file which looks something like this:

import App from '$containers/App';
import '$scss/global.css';
...
render((
  <App />
), document.getElementById('root'));

Since App contains all of my components, which contain all of my css-module imports (import styles from './index.css'), importing in this order effectively imports all css-modules and then my global styles (duh).

The fix is obviously...

import '$scss/global.css';
import App from '$containers/App';
...
render((
  <App />
), document.getElementById('root'));

Additional info

In my case, the problem was accentuated because of using style-loader in my development setup for css-modules and not my global styles. This effectively lead to an inconsistent import order between dev and production. My actual config looked like this:

webpack.config // (BAD - don't use)
{
  // Import container and component index.scss as local CSS Modules
  test: /\.scss$/,
  include: /(components|containers).*\.scss$/,
  loader: [
    isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader',
      options: {
        modules: {
          localIdentName: `${isDevelopment ? '[local]___' : ''}[hash:base64:5]`,
        },
        sourceMap: isDevelopment,
      },
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: isDevelopment,
      },
    },
  ],
},
{
  // Import all other scss as global
  test: /\.scss$/,
  exclude: /(components|containers).*\.scss$/,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    'sass-loader',
  ],
},

This is terribly obvious in hindsight, but somehow all the comments in other threads about "fixing your order" didn't help me much. Thanks @evilebottnawi for your timely responses.

@alexander-akait
Copy link
Member

@sallf big sorry for delay, we need option(s) to allow specify order, if you put your example here #555 we will add to test it, we are working on it

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

2 participants