Imagine:
// a.css
.a { color: #aaa; }
// b.css
.b { color: #bbb; }
// ab.css
.ab {
composes: a from './a.css';
composes: b from './b.css';
}
// ba.css
.ba {
composes: b from './b.css';
composes: a from './a.css';
}
# ab.js
import ab from './ab.css';
render() {
return <div class={ab.ab}>AB</div>;
}
# ba.js
import ba from './ba.css';
render() {
return <div class={ba.ba}>BA</div>;
}
You'd expect AB
to always be #bbb
(because b gets imported last), and BA
to always be #aaa
for the same reason. If you look at both JS files independently, that is. However, since you depend on import order (which can be affected by other imports happening anywhere in the same codebase), you lose control over the inheritance.
This is fine-ish when you know your codebase and can be sure that CSS is always imported in the right order, but the moment someone imports b.css
before you get the chance to import a.css
first, you lose the expected result.
With preprocessors and things like @extends
you don't get this issue because imports just flow linear and deciding what CSS comes first is a lot more explicit. Their documentation explicitly says that the order of inheritance is purely defined by when selectors occur in the CSS.
There's a way to fix this, but it means adding specificity tricks, and adding selectors to the imported CSS selector in the output.
Using the examples above, it'd lead to this (no class name mangling for clarity, together in a gist):
.a, .a.ab-a-specific, .a.ba-a-specific.ba-a-specific-specific { color: #aaa; }
.b, .b.ab-b-specific.ab-b-specific-specific, .b.ba-b-specific { color: #bbb; }
<div class="ab a ab-a-specific b ab-b-specific ab-b-specific-specific">AB</div>
<div class="ba b ba-b-specific a ba-a-specific ba-a-specific-specific">BA</div>
The good things: it works, repetition gzips well. The bad thing: it's ugly, a challenge to implement.
In projects with lots of composing this could lead to huge selectors, since you're basically replacing all inheritance-by-inclusion-order with explicit specificity. Luckily it's only necessary once you start dealing with composing from multiple classes in a single new class, so maybe it's still something to consider? It would vastly increase complexity of the code, since any class would cause new selectors to be added to all classes it composes from and all their parents too, all the way up the tree.
The other option is probably just making no guarantees about import/override order when dealing with composing from multiple classes. Which is also not ideal and basically means it's an unreliable feature.
Maybe I'm thinking too far here, but I think it's something to look into, at least.