Next.js: css-loader + CSS modules pure selectors

Created on 17 Jan 2020  路  22Comments  路  Source: vercel/next.js

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
story 2

Most helpful comment

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.

All 22 comments

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

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.

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

@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

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}>

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

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

@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

Any update on this?

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);
}

@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?

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.

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

I don't know to say except "+1"

I've traced https://github.com/zeit/next.js/issues/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.

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.

@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.

Yes, need to update deps and it can be closed

Fixed in [email protected].

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

@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.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

havefive picture havefive  路  3Comments

YarivGilad picture YarivGilad  路  3Comments

timneutkens picture timneutkens  路  3Comments

pie6k picture pie6k  路  3Comments

jesselee34 picture jesselee34  路  3Comments