treat
treat copied to clipboard
Inoptimal hashing of classnames?
I aim to address the removal of hashing debug names filenames etc, and purely rely on the contents of the class for hashing.
With the notion outlined at https://github.com/seek-oss/treat/blob/ba4cf40dbe037c19fc610c4803abc88e83610278/packages/treat/src/validateSelector.ts#L47 it speaks about having styles only target one element.
With that knowledge, and how css optimization in webpack get applied on a per-chunk basis, we can relatively assume that merging selectors will work, per-chunk.
When looking at the simple example:
export const root = style({
color: 'red'
});
having that in 2 files, will generate 2 css rules. If both of those rules exist in the initial chunk, you'd get 2 rules in the initial css.
Sure, gzip will merge the two
color: red
's, however the rule names will not. I suppose something like single-char-classname will aid in this. But i think my original premise still stand.
I do see that this change would require quite a change as identNames don't have knowledge of its contents. But I do believe this should in theory be possible.
https://github.com/seek-oss/treat/blob/5f5e8381744e1a6d8230b2fb300a6a5356fda48d/packages/treat/webpack-plugin/loader.js#L73-L91
There is an issue where cssnano
is ran over css on a per-module basis, which then cross module optimizations cannot be applied (as you can see from the commented code in my repro). I guess also briefly touched on in https://github.com/seek-oss/treat/issues/109. Perhaps I need to use the webpack optimization for css?
I have create a reproduction example that illustrates my point. In there you'll see a dist/main.css, this should have read .D5CYg,.DsFjG{color:red}
, but this issue wants to go further and simply read .D5CYg{color:red}
, and have both modules consume the same "hash".
https://github.com/maraisr/repro-treat-hashing
This method of hashing was considered in early prototypes. The issue it introduces though is that classes can change their rule order precedence seemingly randomly.
For example, imagine I have two treat files.
// file1.treat.js
export const root = style({
color: 'red'
});
// file2.treat.js
export const blue = style({
color: 'blue'
});
export const red = style({
color: 'red'
});
Then in a file consuming file2.treat
<div className={`${styles.blue} ${styles.red}`} />
You expect the red
style to take precedence as it was defined last. However, if you apply this kind of optimisation you lose that guarantee. This is effectively the same issue that atomic CSS-in-JS libraries face. It can be achieved, but you need a heavier runtime to do so, as you need to remove the clashing values between each class. Otion and probably StyleX use this approach.
// example
<div className={applyStyles(styles.blue, styles.red)} />
We opted not to go this route with treat as we use it to create an atomic library ourselves (see Braid) to limit the overall amount of unique styles.
Note: I wish that CSS worked this way by default TBH. Allowing you to choose the most important classes at the application of the class, rather than by rule order precedence. But that's not my call 🤷♀️
I see your point.. I guess I was naive in that I assumed that .red
will always be red, but yeah, a .blue
later in the file would take over. Which would break, if it was defined correctly per-module, but then merged inter-module. So what if we did something clever here?
I'm thinking out loud here; But what if you hash per-module on the content. So that all color: 'red'
's get the same ident in that module, because that wouldn't matter, because if there was a rule defined later on that overrode the color hash exactly, then its the same behavior if the rule was defined twice, or once.
ie:
.red {color: 'red';}
.blue {color: 'blue';}
.red2 {color: 'red';}
// =>
.red{color:"red"}.blue{color:"blue"}.red2{color:"red"}
// ---
.red {color: 'red';}
.blue {color: 'blue';}
.red {color: 'red';}
// =>
.blue{color:"blue"}.red{color:"red"}
Now this next part I definitely don't know if it'll work; but what if then you track those hashes, if there is a hash with the precedence taken into consideration, a collision could take place. Then add some uniqueness to that class - do that recursively.
I mean in practice, the only way a class could be applied in a component that wasn't there in that module, would be through a prop right, or some other import? But we don't care about that. Only the imports - and those are already lower precedence than its own direct treat files. So the hash collision thing would work.
Granted yeah, I'm not saying treat needs to generate atomic styles, but I do think there is room for some more intelligent hashing and optimizations. I suppose if anything, a per-module content hash de-dupe would be great. Treat doesn't have to do it, but just have the css generated to enable cssnano to do it - it already does in that .red,.red2 {color:"red"}
but still....
Just as a FYI; the optimization wouldn't merge into the first declaration, but rather the last. cssnano playground doesn't have a permalink, but punch in your red, blue example and you'll see that it'll still work.
As always man; love you work and appreciate your time and patience with me!