Description
Background
This is an idea about how to solve the very common author pain point where CSS that is not actually unused gets removed.
Here is a sampling of closed issues on this:
- Add an option to prevent removal of unused CSS selectors/classes #5804
- Unused css selector warning - disable specific parts? #1594
- Change removal of unused CSS to just a warning #7751
In many of these the suggestion is to work around it via :global()
, but of course that does not preserve the scoping that in many of these cases is desirable.
I think a lot of what the discourse is missing is that this could easily happen when integrating existing libraries into Svelte.
For example, D3 + Svelte is the current state of the art for high fidelity interactive visualizations in the dataviz community,
but D3 does not actually know about Svelte. Thankfully, it largely works well with Svelte out of the box, except when it doesn’t.
Case in point, the use case that prompted this: I was generating a chart via D3 and wanted to style ticks via CSS. My HTML looked like this:
<g class="gridlines" transform="translate({margin.left}, 0)" bind:this={yAxisGridlines} />
and the JS that populated the gridlines was:
let yAxisGridlines;
$: {
d3.select(yAxisGridlines).call(d3.axisLeft(yScale));
}
Then, I tried to do this in my CSS:
.gridlines line {
stroke-opacity: .5;
}
Nope, no result. It took some time to realize it was being commented out as "unused".
I even tried a nested version (I'm using Svelte 5):
.gridlines {
line {
stroke-opacity: .5;
}
}
Nope squared this time 😁
Proposed solution: Take advantage of CSS nesting
The reasoning given in many of these cases is that Svelte cannot apply classes to elements it doesn't know about. Fair.
But what about descendants? E.g. in my case, the .gridlines
container was in my CSS, and line
was within it. line
did not actually need any classes for scoping, because it's already scoped via its parent.
Instead of parsing selectors to figure out which ones can work like this and which ones cannot (which is a can of worms I do not wish on anyone), I propose a much easier solution: we take advantage of nesting. If the parent of a nested set of rules is not unused, then the children are automatically also not unused (essentially wrapped in :global()
). This way Svelte 5 can provide a workaround for this longstanding issue in an intuitive way, that involves relatively low implementation complexity.