rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Interface for writing styles and rendering them to CSSOM

Open kof opened this issue 4 years ago • 11 comments

Proposal for a full CSS support that integrates with the component's lifecycle.

Rendered View

kof avatar Apr 20 '20 13:04 kof

Some quick initial thoughts just to give you a sense where my headspace is at atm.

It’s interesting because I think the goal here is simply to use React’s position to standardize on a particular approach to avoid divergence. Rather than there being something in particular that React core as an implementation is able to fix.

That’s where the template syntax itself comes in.

Eg one alternative would be to standardize a different object format that different inputs could target if you wanted to just open up for further exploration with some place to inject in the classic DOM node API.

One particularly tricky case where standardization is important is for sharing components. In that case it’s important that the compilation and bundling strategy works as well. So there needs to be some thoughts into the actual mechanism. If people come up with different incompatible mechanisms then we lose the benefit of standardizing. Meta frameworks (like Next and Gatsby) are actually in a better spot to enforce how this should work end to end.

One way to go about this is standardizing it as a high level api and then React will figure out how to wire it up.

Another way is exposing some new primitive hooks. Eg today it’s not possible to inject global style tags at the perfect time. Either it’s too early in render which can cause bad perf in concurrent mode, or it’s too late because someone may read layout before your layout effect. There also needs to be a way to insert these into a SSR stream. I think there’s still a lot that is suboptimal there that we could explore with user space solutions. What do we need to add to React to help meta frameworks and then they can standardize on a solution on top?

I’ll also note that there’s another school of thought all together that I find quite compelling.

Currently there’s a lot of focus on primitive HTML and expression everything in terms of CSS and HTML tags. However I think the real value of componentization is if we can describe with composable building blocks. A lot of those could be built in. However there will always be a lot that won’t be that needs custom implementations. In those cases the standard React API can actually get in the way. That’s what this (unbaked) proposal is meant to address: https://github.com/reactjs/rfcs/pull/96

So another thought is that maybe all core components should be built with this more imperative style with closer connection to the raw DOM. Maybe there isn’t even a standard React API for bridging HTML semantics like className, onClick and style. In that world, what does this API mean? Would this still have a place as something you pass into one of those components?

sebmarkbage avatar Apr 20 '20 21:04 sebmarkbage

@sebmarkbage

It’s interesting because I think the goal here is simply to use React’s position to standardize on a particular approach to avoid divergence. Rather than there being something in particular that React core as implementation is able to fix.

Partially yes, but also to avoid users having to use a babel plugin or a jsx pragma to make the css prop work, both create some friction. Also (not part of this RFC) it could solve composition by taking the style or css prop similarly to how we take a ref and composing the classes together. This is how Emotion currently does it.

Accepting an object that can describe CSS is basically a new primitive that enables rendering and composition of CSS.

Eg one alternative would be to standardize a different object format that different inputs could target if you wanted to just open up for further exploration with some place to inject in the classic DOM node API.

Standardizing on syntax like the one I did for JSS is something we could consider as well, but it would largely increase the API surface React needs to standardize upon compared to what is in this RFC, because right now the primitive I pass to the host component is {css: 'a {color: red}'}, where css is a CSS string ready to be injected as-is into the CSSOM.

One particularly tricky case where standardization is important is for sharing components. In that case it’s important that the compilation and bundling strategy works as well. So there needs to be some thoughts into the actual mechanism. If people come up with different incompatible mechanisms then we lose the benefit of standardizing.

I agree that's why I think React needs to actually render to CSSOM, making sure the bundled CSS is rendered correctly and supports async mode well. It seems to me that bundling inside of JS bundle works out of the box, so the consumer of a published component will have nothing to set up other than what the already do.

Maybe I missed something in what you said though?

Another way is exposing some new primitive hooks. Eg today it’s not possible to inject global style tags at the perfect time. Either it’s too early in render which can cause bad perf in concurrent mode, or it’s too late because someone may read layout before your layout effect.

Can you please explain the "too early" case? So far I have been using useEffect during SSR and useLayoutEffect on the client to insert style tags, e.g. here

There also needs to be a way to insert these into a SSR stream. I think there’s still a lot that is suboptimal there that we could explore with user space solutions.

Emotion and Styled Components support React's streaming SSR API and output style tags as they go before the components.

Currently there’s a lot of focus on primitive HTML and expression everything in terms of CSS and HTML tags. However I think the real value of componentization is if we can describe with composable building blocks. A lot of those could be built in. However there will always be a lot that won’t be that needs custom implementations. In those cases the standard React API can actually get in the way. That’s what this (unbaked) proposal is meant to address: #96

If I understand correctly you are referring to primitives like react-native has: View, Text etc? I think even if these primitives existed, the need to pass CSS to them will still exist. There still will be many lower level CSS questions regarding CSS rendering, composition, overrides etc, right?

So another thought is that maybe all core components should be built with this more imperative style with closer connection to the raw DOM. Maybe there isn’t even a standard React API for bridging HTML semantics like className, onClick and style. In that world, what does this API mean? Would this still have a place as something you pass into one of those components?

That's interesting because #96 could help implement style tag rendering logic (if that API existed) and that style node could be auto-created when css is passed to a div. It won't solve the primitive interface problem though.

kof avatar Apr 20 '20 23:04 kof

Updated the spec with a bit more details about the primitive, compile target using css-modules example as well as object-based styles.

kof avatar Apr 21 '20 09:04 kof

Just wondering - this RFC is actually about react accepts a special primitive and know how to handle it. This is almost the same as opaqueId - the same special primitive. What if it's a missing part for this RFC - a standardization for operations like this.

CSS is a very opinionated thing, and many companies have developed absolutely different approaches to work with it, especially if css is used as a methodology, not just as a styling tool.

This all falls under Unresolved questions, and these questions might require a personal answers for different cases. Thus making this moment a bit more flexible could help a little.

For example:

  • there is className, could one have classNames?
  • there is a rule to create classNames could it be a function, which could read component props? Well, and context, even better - a theme. Oh, let's not continue.
  • what would happen if an object with .css(as per RFC) is provided
  • a css prop on element? No difference
  • and visa versa

theKashey avatar Apr 21 '20 10:04 theKashey

@theKashey companies can keep using whatever they are using as the spec says, this is not a breaking change, but an attempt to enable 90% of cases by default and the rest 10% using user-land abstractions, while still supporting everything people do today.

In addition, this is a very minimalistic approach and it doesn't cover all possible features one "might" want have, which would blow the scope, but it still keeps the door open for the most things you mentioned to be discussed in the future as a separate effort.

kof avatar Apr 21 '20 10:04 kof

Naming suggestion: @@style in a similar vein to @@iterator instead of css so {'@@style': string} or {'@@style': function}

thysultan avatar Apr 21 '20 11:04 thysultan

@thysultan yeah, was thinking about making this object uniquely identifiable as well, but left it out for now, I think we might want to use an actual symbol ({[React.cssSymbol]: string}), I am gonna add this to the list of unanswered questions to reduce the scope of this RFC

kof avatar Apr 21 '20 11:04 kof

I think that the streaming server Sebastian referred to is the new React Flight work in https://github.com/facebook/react/tree/master/packages/react-flight-dom-webpack.

iamdustan avatar Apr 21 '20 15:04 iamdustan

@kof If it should be unique i think it should only be "pseudo" unique and not "react" unique i.e Symbol.for('@@style'), so other react-like libraries can support it. the underlining abstractuib will then be able to support all of them with one stroke of the pen.

thysultan avatar Apr 21 '20 16:04 thysultan

@thysultan that's a good point @iamdustan I am not familiar with the implementation details of react-flight-dom, but my assumption is that it is still going to be able to output a style tag before a component tag, for each component

kof avatar Apr 21 '20 16:04 kof

I wanted to note that we've been thinking about this space lately again in the context of preparing React 18. There are questions like, when should the CSS injection ideally happen in the React lifecycle, how that should be coordinated with the new streaming SSR (https://github.com/reactwg/react-18/discussions/37), how it should work with Server Components (#188), and so on. We expect to share more of our thinking on this in the coming months, but no concrete updates yet.

gaearon avatar Aug 19 '21 00:08 gaearon