Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface for writing styles and rendering them to CSSOM #159

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

kof
Copy link

@kof kof commented Apr 20, 2020

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

Rendered View

@kof kof changed the title Interface for writing styles which render to CSSOM Interface for writing styles and rendering to CSSOM Apr 20, 2020
@kof kof force-pushed the master branch 3 times, most recently from d6e20cf to 2ef6541 Compare April 20, 2020 14:18
@kof kof changed the title Interface for writing styles and rendering to CSSOM Interface for writing styles and rendering them to CSSOM Apr 20, 2020
@sebmarkbage
Copy link
Collaborator

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: #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?

@kof
Copy link
Author

kof commented Apr 20, 2020

@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
Copy link
Author

kof commented Apr 21, 2020

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

@theKashey
Copy link

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

@kof
Copy link
Author

kof commented Apr 21, 2020

@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.

@thysultan
Copy link

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

@kof
Copy link
Author

kof commented Apr 21, 2020

@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

@iamdustan
Copy link

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.

@thysultan
Copy link

@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.

@kof
Copy link
Author

kof commented Apr 21, 2020

@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

@gaearon
Copy link
Member

gaearon commented Aug 19, 2021

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 (reactwg/react-18#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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants