Problems surrounding SSR injection of <style> and unreliability of :first-child selectors
See original GitHub issueProblem description:
In emotion v10+, a new warning is thrown if you use :first-child
, :nth-child
, or :nth-last-child
selectors in a styled component or css prop value. This is because, when using SSR, the style
element is injected immediately above (prepended) the associated component. (More details.)
There are a number of problems with this approach:
Note: I made a test case demo to illustrate which selectors are unreliable.
- In our codebase and matching years of experience for myself and peers, the “first” selectors are used much more often than the “last”. Making these selectors unreliable is a big blow to those expecting standard CSS features to work.
- The suggested workaround of changing, e.g.
:first-child
to:first-of-type
, only works if all sibling elements are of the same type. This is probably fairly common for components like lists, grids, etc… But it’s far from a guaranteed solution. It could also discourage the use of semantic markup by driving devs to “just use divs” for everything, since it’s a simpler “fix”. - The list of unsafe selectors is incorrect.
- The adjacent sibling pattern,
* + [anything]
, is unreliable, but not listed.- This type of selector may seem somewhat obscure, but it can be rather useful.
:only-child
is unreliable, but not listed.:nth-last-child
is still reliable when prepending thestyle
element and should not be listed.
- The adjacent sibling pattern,
- Consumers not interested in SSR should be able to turn off the warning (#1105)
Suggested solution:
Let’s tackle each problem, in turn:
-
“First” vs. “last” selectors (or prepending vs. appending the
style
element)If emotion injected the
style
element after (appended) the associated component, then the following selectors would be unreliable::last-child
,:nth-last-child
,:only-child
.Compare that to the currently unreliable selectors:
* + [anything]
,:first-child
,:nth-child
,:only-child
.By changing to appending the
style
element, emotion would no longer block use of the common adjacent sibling selector and the “last” (rather than “first”) varieties of the selectors would be affected, which I would argue is a better tradeoff more in line with actual usage of each [citation needed]. -
“of-type” workaround
The warning’s suggestion should be reworded to make clear that it may not work in all scenarios.
-
Incorrect unsafe list of selectors
:only-child
needs to be added (changing this to:only-of-type
is a good workaround, though!- If the
style
element remains prepended- The
* + [anything]
selector pattern needs to be flagged :nth-last-child
needs to be removed
- The
- If the
style
element is appended- Update list to include selectors in (1), above.
-
Mute the unsafe selectors warning
At the very least, emotion should allow consumers to do so.
Alternative solutions:
Another suggested workaround is to somehow replace :first-child
selectors with :first-child:not(style), style:first-child + *
in the styles output. While a clever bit of CSS selecting, this suggestion has two main problems I can think of: 1) it won’t work for non-:first-child
selectors (though maybe we could come up with other rewrites for the others) and 2) replacing :first-child
in a selector is not trivial, as it is affected by whether or not its used on the immediate element being styled or a descendent element.
The maintainers of emotion and its community could also simply decide that making these selectors unreliable is not worth the tradeoff of SSR “just working” and abandon the approach of injecting colocated style
elements entirely. I have no idea how palatable such a change would be, but wanted to enumerate all the options.
This issue is intended to open discussion around these problems. If we can decide on forward actions, I’m happy to help implement wherever I can.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:65
- Comments:63 (35 by maintainers)
Top GitHub Comments
Hello everyone. Just weighing in with some comments and questions…
I appreciate the simplicity and relative elegance of Emotion’s SSR strategy, but I believe that the trade-off of “breaking” many commonly used CSS pseudo selectors to not be worth it.
Some pseudo selector usages can be changed to an alternate, but oftentimes there are others that get unwieldy fast or simply cannot be accommodated. In any of the cases, the updated implementations are unlikely to be the best choice or most easily understood. Further, the console error that suggests to try using nth-of-type is also not very helpful, particularly for those not even doing SSR. #1105.
May I ask what was the impetus to change from the previous SSR implementation? Performance, reliability, usability, difficulty, maintenance? I did not use it and therefore only have so much perspective on it. It seems that if it did not suffer from these issues, that perhaps it was a superior solution, albeit perhaps less elegant. Personally speaking, I would much rather have to deal with a bit of extra SSR configuration code as it is often a one time cost and limited in scope.
Is it possible to opt-out of the new SSR implementation for those cannot accommodate the concessions? I see that it is documented that the old SSR API’s can be used with Emotion 10, however with the caveat that is should only be done for compatibility and migration purposes. I can’t imagine that you would want to support multiple API’s long term though. It also would not address #1105.
A few comments on some of the other proposed ideas…
I don’t think rewriting selectors is a good idea because it’s confusing when debugging and also complex and error-prone.
The server-rendering of Emotion 10 unfortunately also messes up the following selector:
I agree with @brentertz that sacrificing common CSS selectors for easier setup is not worth it. At least not for us.