-
-
Notifications
You must be signed in to change notification settings - Fork 26.9k
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 Modules localIdentName cleanup #3965
Conversation
Simplify localIdentName by removing path à la: facebook#2285 (comment)
Only use hashes for CSS Modules classes in production. When deterministic classes or IDs need to exist in production for testing or other automated tools additional static classes can be added, decoupled from styling classes. facebook#2285 (comment)
Looks like the integration tests need to be updated. |
@ro-savage Thoughts? |
Well that's embarrassing.
I'll use this as a great opportunity to get to know your e2e tests. 😉 |
You can look at where they were added (I assume in CSS Modules PR). |
TL:DR And I don't understand why people want a hash over path/filename/classname, except that they are used to it and/or its weird for them to see a long classname. You go from having something useful to humans to having something that is only useful for machines. There is some mis-information in the original PR thread (which I contributed to). Hashes are deterministic as they are
Hashes are deterministic. I don't understand how it affects being modular at all. Your classes name are matched with your components, and never affect anything outside outside of where its imported.
We already have classes with can use for this. Why add another layer of complexity (without a benefit).
Hashed name can still be targeted exactly the same. But beyond that, why would we artificially make it harder for people to do their job or write tests? Their are already so many barriers to writing tests. Why add another one. If people want to use a data-attribute we aren't stopping them. The only reason that made sense (to me) was smaller file sizes. I ran the difference on one of my projects. We use Parsing theoretically takes longer, but when I tried to measure it, the random changes in parsing time were much greater than any affect changing the css names had. So I was unable to measure any real-world difference. What has been great about using non-hashed names, is using the developer elements/html tab, it has been extremely easy to identify components. Reasoning about and talking about targets as something that makes sense in english vs a random string is much easier also. Having only The compromise would be @geelen may have thoughts and has more expertise than me.
|
@@ -269,7 +269,7 @@ module.exports = { | |||
options: { | |||
importLoaders: 1, | |||
modules: true, | |||
localIdentName: '[path]__[name]___[local]', | |||
localIdentName: '[name]-[local]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would allow clashes in names. Its unlikely, but could happen, so add it as a possibility?
Having difference between the final output and dev, can also cause friction as it makes it much harder to look at the production site and find the issue when running in dev mode because all your class names are unrelated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree I would leave it as is
@@ -306,7 +306,7 @@ module.exports = { | |||
minimize: true, | |||
sourceMap: shouldUseSourceMap, | |||
modules: true, | |||
localIdentName: '[path]__[name]___[local]', | |||
localIdentName: '[hash:base64:5]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still deterministic, it is just smaller and no longer useful for a human to read/look at.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is just smaller and no longer useful for a human to read/look at
Yes this is exactly the point. Developers should not use those values. And it is nice that they are small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Developers should not use those values.
Sometimes you have to overwrite some styles but you have no access to this this component (eg component library), so the only way that you have - is to use this localIdentName
, eg [class*="foo__bar__baz"]
Will be better to have ability to configure this
To add to @ro-savage's point, containerized deploy systems with immutable builds make the issue of obfuscated classnames quite a bit worse in my experience. Everything that doesn't run on the local machine gets prod classnames while not actually being prod. This leads to increased troubleshooting times in many cases, and a steeper learning curve for developers who haven't worked in this setup yet. For me and any team I've worked on, it would take much more than a fraction of a percent reduction in bundle size to offset these woes. |
Maintaining two sets of names (component names for development and "public API" names via data attributes for marketing tools) requires twice more effort with unclear benefits. Given we seek consensus, I'd vote for |
We tried a lot of different approaches. But ended up with solution which gave us uniq class names in dev and mangled ones in production. Uniqueness and readability of generated class names we achieved by code structure - all components have uniq file names, like Then, for production we additionally use https://github.com/JPeer264/node-rename-css-selectors |
dev: |
I vote in favour of dev/prod parity in class names. Furthermore readable class names in both environments are useful too. This was a pattern we settled upon independently after some trial and error and running some apps in production, troubleshooting bugs etc. Also, our component folder and file structure look like,
So not having |
None of these ideas are wrong, or worse, in fact most of them are really cool and make a lot of sense. But CRA is about sensible defaults that minimize learning curves and the need to retain extra configuration knowledge. With both
Thats it, then it just works, theres no clashes, no weirdness, no unintelligible names, no difference between dev and production, no following rules or naming conventions. Most of the other suggestions introduce complexity, cognitive load and gotchas. That is fine for your own personal or companies project, where you know the rules and the trade offs but not for something that sells itself as Just create a project, and you’re good to go. |
For those who want the saving and benefits of shorter names/smaller file size, use @klimashkin excellent method of running your generated CSS and JS through @JPeer264's rename-css-selectors. |
fwiw |
Having used css modules for quite some time, I can say that using hashes in prod - while it might be good for file size - can result in a really poor debugging experience. I tried using hashes to obfuscate the code a bit and immediately regreted doing that, when sentry reports stopped being readable. |
Another discussion happening at the same time in #3972 |
Plenty of thoughts, not sure about expertise any more! The only thing I want to add is that collisions are earth-shatteringly bad. Like, fundamentally-undermine-the-premise-of-CSS-modules bad. To that end, the idea of a class That said, I'm not convinced that a classname
(assuming This is relatively short, deterministic, vaguely human-targetable ( Or, you know, just use emoji 💫😉💫 |
@geelen Css-modules users usually don't have files with the same name. And in LoginScreen.css you will have classes for Button.css that override or compose each other using theming, like in https://github.com/javivelasco/react-css-themr |
I don't think that's true. See @jedrichards' comment above:
But even if you're right and most people adopt or could learn that convention, the problem is the severity of the failure—building a component with a CSS processor that "guarantees" no clashes and suddenly you have CSS rules you didn't write and can't track down. Yes, using a single "Button" and applying theming or something is better, for a bunch of reasons, but baking-in a requirement that no two CSS files in your project have the same filename seems like a disproportionate reponse. |
@geelen @jedrichards
and
is a "choice of experience" ; ) After all, css-modules is just a convention, so anyway you have to extend this convention in particular project to achieve human-readable and uniq class names at the same time. I think we found it making file structure a convention too as I described and gone even further with node-rename-css-selectors in production |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks to everybody for the discussion.
I can get behind [name]__[local]:[hash]
, inspired by #3965 (comment). I agree it strikes a nice balance between all the requirements: debugging, class name length, and programmable targeting without being too obtuse.
Of course hash
must be calculated from the full path. (I believe it is in webpack.)
Bonus points if we can "fall back" to the closest folder name if name
is index
. I think this should be doable by specifying a custom getLocalIdent
function as documented here.
@klimashkin css modules is a way better convention than wierd naming schemes like BEM though. |
Please let's keep this PR on topic. |
@lnhrdt - as per @gaearon suggestion, you might try something along these lines: {
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
getLocalIdent: (context, localIdentName, localName, options) => {
// Use the filename or folder name, based on some uses the index.js / index.module.css project style
const fileNameOrFolder = context.resourcePath.endsWith('index.module.css') ? '[folder]' : '[name]'
// Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique.
const hash = loaderUtils.getHashDigest(context.resourcePath + localName, 'md5', 'base64', 5)
// Use loaderUtils to find the file or folder name
const className = loaderUtils.interpolateName(context, fileNameOrFolder + '_' + localName + '__' + hash , options)
// remove the .module that appears in every classname when based on the file.
return className.replace('.module_', '_')
},
minimize: true,
sourceMap: shouldUseSourceMap,
},
}, |
@@ -306,7 +306,7 @@ module.exports = { | |||
minimize: true, | |||
sourceMap: shouldUseSourceMap, | |||
modules: true, | |||
localIdentName: '[path]__[name]___[local]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per suggestions from Dan, you can try something along these lines for both dev and prod.
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
getLocalIdent: (context, localIdentName, localName, options) => {
// Use the filename or folder name, based on some uses the index.js / index.module.css project style
const fileNameOrFolder = context.resourcePath.endsWith('index.module.css') ? '[folder]' : '[name]'
// Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique.
const hash = loaderUtils.getHashDigest(context.resourcePath + localName, 'md5', 'base64', 5)
// Use loaderUtils to find the file or folder name
const className = loaderUtils.interpolateName(context, fileNameOrFolder + '_' + localName + '__' + hash , options)
// remove the .module that appears in every classname when based on the file.
return className.replace('.module_', '_')
},
minimize: true,
sourceMap: shouldUseSourceMap,
},
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also "hide" this function into react-dev-utils/getCSSModuleLocalIdent
so that it doesn't get duplicated in both places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ro-savage Thank you so much. Was on my todo list to use getLocalIdent
to solve the src/component/button/index.module.css
or src/component/button/button.module.css
class naming convention headache.
What a great conversation this has been, I've learned a lot. I plan to tidy this up tonight after work based on @gaearon's suggestions and @ro-savage's examples, but if someone beats me to it to keep this moving I wouldn't be offended. |
Did you get a chance to look at this yet? |
@lnhrdt - Would be great opportunity for you to contribute to CRA. However, if you aren't going to have time. Just let me know, and I'll put in the PR for this. |
@ro-savage thanks for the bump. I'll push something up this weekend. 👍 |
@lnhrdt - Thanks for doing the original PR. If you haven't had time to update it by the end of the weekend, then I'll submit a seperate PR so we can get this all merged for the upcoming 2.0 release. Please just reach out if there is something you are stuck on, have a question or need some assistance with it. |
@ro-savage thank you for helping wrap this up. #4192 looks great. |
I know this issue has been closed but I felt like I had to vent my frustration and add to those voices who were hoping for production build class names to be just hashes. Having read through all (or at least most) CSS module related discussions here it's sad to see that pretty much everyone who wanted to have hashes were shot down by certain people just because they "see no reason to change". That's the attitude! Hashes in production builds was the only feature I was looking forward to in CRA2 but after seeing that we will have full paths renders it pointless for me. I never had a problem with using BEM notation in my JSX so switching from that to The whole point of using CSS modules for me personally is to "hide" my component naming conventions, behaviour and structure from the prying eyes of "Chrome inspectors" but if they can see stuff like I know there's not much point in talking any more but all I can say is I couldn't be more disappointed with this decision. Don't even get me started on "I can't read class names in production" argument. It just doesn't make any sense. Somehow Google, Facebook and many other companies and users can deal with it just fine but apparently not us and frankly your code should be tested by the time it gets there. I wish things like these were actually configurable without ejecting instead of them being imposed on us but alas. |
Cleaning up CSS Modules localIdentName in dev and prod configurations in an attempt to provide ease of use for developers in development and encourage best practices in production.
Context and rationale in the commit messages!