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

css-loader + CSS modules pure selectors #10142

Closed
Pegase745 opened this issue Jan 17, 2020 · 25 comments · Fixed by #16973
Closed

css-loader + CSS modules pure selectors #10142

Pegase745 opened this issue Jan 17, 2020 · 25 comments · Fixed by #16973
Assignees
Milestone

Comments

@Pegase745
Copy link

Pegase745 commented Jan 17, 2020

Bug report

Describe the bug

css-loader error on build.

To Reproduce

After migrating to 9.2 and testing the CSS modules features, the below configuration started erroring.

colors.css (containing only values)

@value customGreen: #3ba150;

Component.module.css

@value customGreen from "./colors.css";

.custom {
  color: customGreen;
}

I am having:

./Component.module.css (./node_modules/css-loader/dist/cjs.js??ref--5-oneOf-2-1!./node_modules/postcss-loader/src??__nextjs_postcss!./Component.module.css)
CssSyntaxError
Selector ":import("./colors.css")" is not pure (pure selectors must contain at least one local class or id)

Expected behavior

Well it did seem to work when I had

next.config.js

module.exports = withPlugins(
  [
    [
      withCSS,
      {
        cssLoaderOptions: {
          localIdentName: '[name]_[local]_[hash:6]',
        },
        cssModules: true,
      },
    ],
  ],
  nextConfig
);

Not sure if there's some tweaking to do some where, not a champ on CSS modules stuff.

I've tried to understand this test but no luck.

System information

  • Version of Next.js: 9.2
@xaxist
Copy link

xaxist commented Jan 18, 2020

I am trying out the newly released v9.2 and is facing the exact same issue. I am neither able to make the global CSS work nor the local using the module system.

Global CSS doesn't apply but everything compiles correctly. The module-based system throws the "Selector "nav" is not pure (pure selectors must contain at least one local class or id)" error.

I'm following the steps as mentioned in the blog article

@cedric-marcone
Copy link
Contributor

I reproduced the problem you're facing by using css modules @value.
=> It doesn't seem to be supported yet by nextjs :(

For it to be supported, it would need to use the postcss-modules-valuespackage in the css build step.

Looking at next package.json, I can't see the dependency.

@Pegase745
Copy link
Author

Warning: Please remove the postcss-modules-values plugin from your PostCSS configuration. This plugin is automatically configured by Next.js.

@cedric-marcone It seems to be included

@cedric-marcone
Copy link
Contributor

@Pegase745 : You're right, I added a postcss config file with the module and I'm having the same warning.

=> Currently css modules in next 9.2 stop working when using @value.
=> It is not caused by the absence of postcss-modules-values

@tommyboylab
Copy link

tommyboylab commented Jan 24, 2020

I've also found the same issue after migrating my project to the latest Next Canary (9.2.1-canary.11) using SCSS modules.

1:0) Selector "nav" is not pure (pure selectors must contain at least one local class or id)
> 1 | nav {
    |               ^
  2 |   width: min-content;
  3 |   height: min-content;

I'm not sure what the error means but would be happy to modify my SCSS to solve the issue


Update

I solved my issues by moving away from implicit HTML calls (ex: nav and <nav> ) for specific elements, instead calling my components with classes (ex .nav and following up with <nav className={s.nav}>

@Timer Timer added this to the 9.2.x milestone Jan 24, 2020
@agconti
Copy link

agconti commented Jan 26, 2020

Same here, I can push up a demo project that reproduces the error if that's helpful.

@Pegase745
Copy link
Author

@agconti that would be great if it doesn't take too much time!

@agconti
Copy link

agconti commented Jan 26, 2020

@Pegase745 here's a repo which minimally reproduces the error: https://github.com/agconti/next-js-css-modules-unable-to-use--global-with-css-modules

Steps to reproduce:

  • clone the repository: git clone https://github.com/agconti/next-js-css-modules-unable-to-use--global-with-css-modules
  • install the deps with npm i
  • navigate to localhost:3000 and refresh the page
  • See the error in the server's logs
 error ] ./colors.module.css (./node_modules/css-loader/dist/cjs.js??ref--5-oneOf-2-1!./node_modules/postcss-loader/src??__nextjs_postcss!./colors.module.css)
CssSyntax error: Selector ":global" is not pure (pure selectors must contain at least one local class or id) (1:1)

> 1 | :global {
    | ^
  2 |     body: {
  3 |         background-color: red;
ModuleBuildError: Module build failed (from ./node_modules/css-loader/dist/cjs.js):
CssSyntaxError

@lvkins
Copy link

lvkins commented Mar 6, 2020

Any update on this?

@lvkins
Copy link

lvkins commented Mar 7, 2020

I had no time to dig deep into the next.js css-modules implementation, but wanted to share this:

.container {
    --color-fancy: #00deee;
    --color-primary: #3b9aa1;
}

Then simply apply them in your descendants module classes:
.foo { color: var(--color-fancy); }

If you want to use separate file for the variables, put the top level class that contains variables in a separate file and then compose:

.foo {
  composes: container from "./colors.css";
  color: var(--color-fancy);
}

@simonsmith
Copy link

@agconti I've created a fork of your reproduction to hopefully illustrate the issue more accurately - https://github.com/simonsmith/next-js-css-modules-unable-to-use--global-with-css-modules

It's quite correct that an error should be thrown when using :global as that is the intention of the pure option, to prevent accidental leaking of styles into other components.

Some examples can be seen in the pure mode unit tests found in postcss-modules-local-by-default (which css-loader uses) - https://github.com/css-modules/postcss-modules-local-by-default/blob/master/test.js#L393-L428

The issue seems to be more when making use of the @value keyword which I've updated the fork to show. Now we see the following error:

Selector ":import("../colors.css")" is not pure (pure selectors must contain at least one local class or id)

So it seems as though the :import selector is at fault, despite there being a test to say it should be ignored: https://github.com/css-modules/postcss-modules-local-by-default/blob/4b765b15707e53099340a74203c535539041af93/test.js#L326-L329

Interestingly if I add the following test to postcss-modules-local-by-default:

  {
    should: 'ignore :import statemtents',
    input: ':import("~/lol.css") { foo: __foo; }',
+   options: {mode: 'pure'},
    expected: ':import("~/lol.css") { foo: __foo; }',
  },

I see the exact same error! This makes me think it may be a bug in postcss-modules-local-by-default

I can open an issue on that repository to track this cc @evilebottnawi Do you agree?

@HoraceShmorace
Copy link

HoraceShmorace commented Mar 9, 2020

I solved my issues by moving away from implicit HTML calls (ex: nav and <nav> ) for specific elements, instead calling my components with classes (ex .nav and following up with <nav className={s.nav}>

I've been attempting to migrate our codebase onto Next, but just today discovered that our SCSS implementation breaks because all of our selectors have to be pure (and plenty are not). While I get why pure selectors would be ideal, it makes it impossible for large, enterprise projects full of "legacy" SCSS to migrate. I'll never get funding for a tech debt effort to update 100+ scss files. There should be a flag to turn off pure mode.

@ematipico
Copy link

Same here. At present we can't migrate to this built-in feature because of this problem.

@artalar

This comment has been minimized.

@delicatesther
Copy link

I've traced #11629 back to this issue as well.

Our setup is having our colour and breakpoint variables declared in a config which is shared between JS, local and global scss alike. A combination of :export and @use allows us to do that. The 'pure' mode on CSS modules breaks this on our end.

I'm also worried what will happen when I try to apply keyframes declared in the global CSS in the modules, I'm sure I would need a :global flag for that, too.

Finally; I cannot adjust the exported class names that CSS modules generate. This is causing inconsistencies between my regular build and Storybook, which supports my own CSS modules settings.

Basically; we need the ability to adjust CSS loader options.

@chrisvxd
Copy link

chrisvxd commented Jun 24, 2020

Overriding the default webpack config worked for me.

To override import behaviour for .module.css (or sass -- thanks to @nkalinov via this comment):

next.config.js

/**
 * Stolen from https://stackoverflow.com/questions/10776600/testing-for-equality-of-regular-expressions
 */
const regexEqual = (x, y) => {
  return (
    x instanceof RegExp &&
    y instanceof RegExp &&
    x.source === y.source &&
    x.global === y.global &&
    x.ignoreCase === y.ignoreCase &&
    x.multiline === y.multiline
  );
};

module.exports = {
  webpack: (config) => {
    const oneOf = config.module.rules.find(
      (rule) => typeof rule.oneOf === 'object'
    );

    if (oneOf) {
      const moduleCssRule = oneOf.oneOf.find(
        (rule) => regexEqual(rule.test, /\.module\.css$/)
        // regexEqual(rule.test, /\.module\.(scss|sass)$/)
      );

      if (moduleCssRule) {
        const cssLoader = moduleCssRule.use.find(({ loader }) =>
          loader.includes('css-loader')
        );
        if (cssLoader) {
          cssLoader.options.modules.mode = 'local';
        }
      }
    }

    return config;
  },
};

To override all CSS import behaviour:

next.config.js

module.exports = {
  webpack: (config) => {
    const oneOf = config.module.rules.find(
      (rule) => typeof rule.oneOf === 'object'
    );

    const fixUse = (use) => {
      if (use.loader.indexOf('css-loader') >= 0 && use.options.modules) {
        use.options.modules.mode = 'local';
      }
    };

    if (oneOf) {
      oneOf.oneOf.forEach((rule) => {
        if (Array.isArray(rule.use)) {
          rule.use.map(fixUse);
        } else if (rule.use && rule.use.loader) {
          fixUse(rule.use);
        }
      });
    }

    return config;
  },
};

If you need to import global CSS, you can configure it to use global and import the styles via a custom pages/_app.js file.

@fdintino
Copy link

@Timer the fix for this issue landed in v3.0.3 of postcss-modules-local-by-default (css-modules/postcss-modules-local-by-default#23). Updating css-loader in @zeit/next-css to "^4.2.1" would effectively resolve this issue.

@alexander-akait
Copy link
Contributor

Yes, need to update deps and it can be closed

@Timer
Copy link
Member

Timer commented Sep 9, 2020

Fixed in [email protected].

@cleversprocket
Copy link

cleversprocket commented Sep 10, 2020

@Timer sorry for the ignorance, but when will this release (not as prerelease)?

@naveen-bharathi
Copy link

naveen-bharathi commented Sep 20, 2020

@Pegase745 @Anupsarode @cedric-marcone @tommyboylab @agconti @cleversprocket @HoraceShmorace
You are receiving this error because you are using html tags directly instead of classnames or ids in a file extension that is probably [filename].module.(css | scss | sass)

File extensions with *.module.(css | scss | sass) are css modules and they can only target elements using classnames or ids and not using tag names. Although this is possible in other frameworks like create-react-app, it is not possible in next-js (as of now).

My suggestion is using these html selector css in a separate file that doesn't contain the name like '.module' in it.
Example: [filename].(css | scss | sass) --> styles.scss

And after doing that, instead of importing like this

import styles from './styles.module.scss';

import like this

import './styles.scss';

This will not through any errors.

HitoriSensei pushed a commit to HitoriSensei/next.js that referenced this issue Sep 26, 2020
@akd-io
Copy link
Contributor

akd-io commented Oct 19, 2020

Thank you for that clarification @naveen-bharathi. That was exactly my problem. I knew that styling html tags wasn't possible in modules, but ignored the error message because my html tag was styled in a non-module .scss file. What I had forgotten was that I was importing the non-module .scss file in a .module.scss file, obviously causing the error.

@cleversprocket
Copy link

@naveen-bharathi the error you mention is different. CSS Modules can only export classes but you can use tags as part of the selector. I'm doing this in my next.js application and elsewhere and it works fine. For instance:

/*
  The `a` and `span` are allowed because they come after the class
*/
.myClass a span {
 color: red;
}

/*
  This is not allowed because only classes are allowed as top level exports
*/
a {
  color: blue;
}

If this weren't allowed then all sorts of things wouldn't be allowed, like pseudo selectors for instance.

@iiison
Copy link

iiison commented Jan 2, 2022

@chrisvxd I changed the value to local as you suggested, and now there's no error in the console as well. But my styles are getting omitted. Am I doing it wrong?

I just copied and pasted your code in next.config.js file. and importing my CSS file(main.module.css) in _app.js.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Feb 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.