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

Allow users to configure options of css-loader #10584

Closed
loteoo opened this issue Feb 19, 2020 · 26 comments
Closed

Allow users to configure options of css-loader #10584

loteoo opened this issue Feb 19, 2020 · 26 comments

Comments

@loteoo
Copy link

loteoo commented Feb 19, 2020

Feature request

Allow users to configure options of css-loader or override options of postcss-modules.

Is your feature request related to a problem? Please describe.

With the previous release of Next.js (9.1), I was using the next-css plugin to use CSS modules and the cssLoaderOptions as seen here.

My config looks like this:

module.exports = withCSS({
    cssModules: true,
    cssLoaderOptions: {
        localIdentName: '[local]__[hash:base64:5]',
        camelCase: true,
    },

Moving to 9.2, I would like to stop using the next-css plugin and instead use the out-of-the-box support from the new release, but I have not found a way to set the camelCase option to true, which breaks my app without it. (I use the camelCase transform everywhere)

The localIdentName would also be nice to be able to configure, since it gives users opportunity to change the resulting CSS class names that end users see, but it's more of a "nice to have" at this point. Not a deal breaker.

Describe the solution you'd like

Doing something like what Gatsby.js does would be great:
https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-postcss#postcss-plugins (scroll down at the end)

In Next.js, this could be by supporting a cssLoaderOptions object to the postcss.config.js file.
Example:

postcss.config.js

module.exports = {
    cssLoaderOptions: {
        localIdentName: '[local]__[hash:base64:5]',  // Easier debug
        camelCase: true,  // Respect common style guides in both CSS and JS
        // More options could go here...
    },
    plugins: {
        'postcss-preset-env': {},
        'postcss-nested': {},
    }
};

Describe alternatives you've considered

Being able to change the configuration of postcss-modules in the postcss.config.js file would also cover most cases. It is currently "already configured by next.js", but being able to override it could give users many more options to play with, such as camelCase, generateScopedName, globalModulePaths which are all very sensible options with real use cases.

More context and motivation behind this request

I know Next.js is trying to make life easier for it's users by making a lot of choices for them, but it should always be possible for the users to override those choices if needed. Going too far in this direction will be like CRA all over again. I don't want to start "ejecting" out of parts of Next.js.

I will keep using next-css until these configurations are supported, but dropping it from my app would be very appreciated as it sounds like it is going to be deprecated in the future.

Bonus: why do I care about the camelCase option

Using dashes in CSS class names is a solid convention that has been around since the beginning of time in the land of CSS. Using camelCase properties in javascript is also the way to go (common style-guides and linters will enforce this). Since CSS modules bridges the gap between the two worlds, it makes a lot of sense to give this option.

@martpie
Copy link
Contributor

martpie commented Feb 26, 2020

Looks like a duplicate of #10339

@davidgolden
Copy link
Contributor

davidgolden commented Apr 10, 2020

@martpie #10339 and the related PR adds support for scss loader options but not css loader options. This would help solve many of the open issues regarding the enforced pure css modules option.

@neo
Copy link

neo commented May 1, 2020

Yes! localIdentName name please!

@ericzwong
Copy link

the same problem for me. I'm migrating CRA project to nextjs, I have encountered the camelCase of css module problem

@ShanonJackson
Copy link

@wangzhe1995

module.exports = {
	...nextConfig,
	webpack(config, { dev, isServer }) {
		config.module.rules[1].oneOf.forEach((moduleLoader, i) => {
			Array.isArray(moduleLoader.use) && moduleLoader.use.forEach((l) => {
				if(l.loader.includes("css-loader")) {
					l.options = {
						...l.options,
						localsConvention: "camelCaseOnly"
					}
				}
			});
		});
		config.node = { fs: "empty" };
		return config;
	},
}

@ghost ghost mentioned this issue Aug 3, 2020
@ghost
Copy link

ghost commented Aug 4, 2020

I open a discussion about this: #15818

@Timer
Copy link
Member

Timer commented Sep 12, 2020

Closing in favor of above RFC. Thanks!

@Timer Timer closed this as completed Sep 12, 2020
@IRediTOTO
Copy link

@loteoo Can you post an example you are using to do that ?

@therealtx
Copy link

therealtx commented Jan 18, 2021

Yes! localIdentName name please!

Did you found any solution for that?

@picardplaisimond
Copy link

I also need a solution for localIndentName.

@ShanonJackson
Copy link

ShanonJackson commented Jan 23, 2021

Again you'll want to do something like this just modify your webpack config in next.config.js to whatever you want, this is my personal one I use to modify exportLocalsConvention so i can use kebab-case in .scss files and camelCase in my components.

This version should work on the latest version of NextJS, the previous version will only work on versions below 10 I believe.

webpack(config, { dev }) {
  config.module.rules[1].oneOf.forEach((moduleLoader, i) => {
    Array.isArray(moduleLoader.use) &&
      moduleLoader.use.forEach((l) => {
        if (l.loader.includes("css-loader") && !l.loader.includes("postcss-loader")) {
          l.options = {
            ...l.options,
            modules: {
              ...l.options.modules,
              exportLocalsConvention: "camelCaseOnly",
            },
          };
        }
      });

@AnonOnr
Copy link

AnonOnr commented Mar 29, 2021

@martpie
Copy link
Contributor

martpie commented Mar 29, 2021

@AnonOnr Be wary that that this could break any time if next.js decides to change their Webpack config.

@0x54321
Copy link

0x54321 commented Apr 19, 2021

For the Webpack 5 config (with Next.js 10.1.3), I had to use config.module.rules[2] in the above code.

Better yet, to make it more future-proof:

    config.module.rules
      .find(rule => rule.oneOf)

@PsyGik
Copy link

PsyGik commented May 19, 2021

Unfortunately the workarounds did not work for me with *.modules.scss. This is what ended up working:

function cssLoaderOptions(modules) {
  const { getLocalIdent, ...others } = modules; // have to remove getLocalIdent otherwise localIdentName doesn't work 🤷🏽 
  return {
    ...others,
    localIdentName: "[hash:base64:6]",
    exportLocalsConvention: "camelCaseOnly",
  };
}
module.exports = {
  basePath: process.env.NEXT_PUBLIC_BASE_PATH,
  webpack: (config, options) => {
    config.module.rules[1].oneOf.forEach((moduleLoader) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes("css-loader") &&
            !l.loader.includes("sass-loader") &&
            !l.loader.includes("postcss-loader")
          ) {
            l.options = {
              ...l.options,
              modules: cssLoaderOptions(l.options.modules),
            };
          }
        });
    });
    return config;
  },
};

@tommypater
Copy link

tommypater commented May 19, 2021

Hello, config.module.rules[1] is for webpack 4 and config.module.rules[2] for webpack 5, rigth ?

@salyadav
Copy link

Trying this for nextjs with styled-components. Its simply aint working! Any idea on what i would be missing?

config.module.rules.forEach(({ oneOf }) => {
        if (Array.isArray(oneOf)) {
          oneOf.forEach(({ use }) => {
            if (Array.isArray(use)) {
              use.forEach(({ loader, options }) => {
                if (
                  loader.includes('css-loader') &&
                  !loader.includes('postcss-loader') &&
                  typeof options.modules === 'object'
                ) {
                  // console.log('loader: ');
                  // console.log(loader);
                  delete options.modules.getLocalIdent;
                  options = {
                    ...options,
                    modules: {
                      ...options.modules,
                      getLocalIdent: (path, _, name) => {
                        //JUST DUMMY for debugging. Replace with actual function
                        return name;
                      },
                      localIdentName: '[name]',
                    },
                  };
                  // console.log(options.modules.);
                }
              });
            }
          });
        }
      });

Doing something like this.

@zzfn
Copy link

zzfn commented Jun 14, 2021

  webpack: (config) => {
    config.module.rules[2].oneOf.forEach((moduleLoader) => {
      if (Array.isArray(moduleLoader.use)) {
        moduleLoader.use.forEach((item) => {
          if (item.loader.includes('css-loader') && !item.loader.includes('postcss-loader')) {
            item.options.modules.exportLocalsConvention = 'camelCaseOnly';
          }
        });
      }
    });
    return config;
  },

it works

@MickCoelho
Copy link

  webpack: (config) => {
    config.module.rules[2].oneOf.forEach((moduleLoader) => {
      if (Array.isArray(moduleLoader.use)) {
        moduleLoader.use.forEach((item) => {
          if (item.loader.includes('css-loader') && !item.loader.includes('postcss-loader')) {
            item.options.modules.exportLocalsConvention = 'camelCaseOnly';
          }
        });
      }
    });
    return config;
  },

it works

Unfortunately doesn't work anymore with next v11

@ShanonJackson
Copy link

ShanonJackson commented Jun 15, 2021

@MickCoelho sometimes the position of "oneOf" changes, that code is by no means a robust solution but normally just means every few versions or so if oneOf's index within the config changes you'll have to update it.

To find where oneOf is i normally just go console.log(config.module) and look for the index.

The new code is....

config.module.rules[3].oneOf.forEach((moduleLoader, i) => {
	Array.isArray(moduleLoader.use) &&
		moduleLoader.use.forEach((l) => {
			if (l.loader.includes("css-loader") && !l.loader.includes("postcss-loader")) {
				l.options = {
					...l.options,
					modules: {
						...l.options.modules,
						exportLocalsConvention: "camelCaseOnly",
					},
				};
			}
		});
});

@MickCoelho
Copy link

Yeah, I did find that out too.
While that solution works, it's kinda risky and is definitely not viable (at least for my current projects), so I'll probably just give up on camel-case until we have the possibility to nicely configure the css-loader

@PsyGik
Copy link

PsyGik commented Jun 16, 2021

@ShanonJackson : Maybe not depend on the index? Something like this:

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

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

      if (moduleSassRule) {
        const cssLoader = moduleSassRule.use.find(({ loader }) =>
          loader.includes('css-loader')
        );
        if (cssLoader) {
          // Use the default CSS modules mode. Next.js use 'pure'. Not sure of all implications
          cssLoader.options = {
            ...cssLoader.options,
            modules: cssLoaderOptions(cssLoader.options.modules),
          };
        }
      }
    }

    return config;
  },
};

P.S: This is for *.scss. For CSS, add a new regex test.

Oh and the regexEqual method:

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

@LGmatrix13
Copy link

LGmatrix13 commented Jul 20, 2021

Does anyone have an updated solution? This works for me, but fails to apply styles from global.css with class name selectors:

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    config.module.rules[2].oneOf.forEach((moduleLoader, i) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes('\\css-loader') &&
            !l.loader.includes('postcss-loader')
          ) {
            const { getLocalIdent, ...others } = l.options.modules;

            l.options = {
              ...l.options,
              modules: {
                ...others,
                localIdentName: '[hash:base64:6]',
              },
            };
          }
        });
    });
    return config;
  },
};

Source: https://stackoverflow.com/questions/66744765/edit-css-loaders-localidentname-in-nextjs-to-hide-class-names-from-users/66744766#66744766

Any tips? This is making me pull my hair lol

@AnonOnr
Copy link

AnonOnr commented Aug 29, 2021

This one works for Nextjs version 11: (https://stackoverflow.com/questions/66744765/how-to-hash-css-class-names-in-nextjs)

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    config.module.rules[3].oneOf.forEach((moduleLoader, i) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes("\\css-loader") &&
            !l.loader.includes("postcss-loader")
          ) {
            const { getLocalIdent, ...others } = l.options.modules;

            l.options = {
              ...l.options,
              modules: {
                ...others,
                localIdentName: "[hash:base64:6]",
              },
            };
          }
        });
    });
    return config;
  },
};

@dunklesToast
Copy link
Contributor

dunklesToast commented Oct 26, 2021

It's broken with Next.js 12 again

config.module.rules[2].oneOf.forEach((moduleLoader) => {
    Array.isArray(moduleLoader.use) &&
      moduleLoader.use.forEach((l) => {
        if(!l.loader) return;
        if (l.loader.includes('css-loader') && !l.loader.includes('postcss-loader')) {
          const { getLocalIdent, ...others } = l.options.modules;

          l.options = {
            ...l.options,
            modules: {
              ...others,
              localIdentName: "[hash:base64:8]",
            },
          };
        }
      });
  });

should work but i somehow get an error that getLocalIdent is not a function and was not able to fix it yet.

EDIT: Using this function as getLocalIdent works 🤷🏻

@romidane
Copy link

@dunklesToast try adding a \ to the css-loader

if (l.loader.includes('\\css-loader') && !l.loader.includes('postcss-loader')) {
   ...
}

@vercel vercel locked as off-topic and limited conversation to collaborators Nov 26, 2021
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.