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

ReactCSSDOM #15

Open
andywer opened this issue Jun 17, 2018 · 5 comments
Open

ReactCSSDOM #15

andywer opened this issue Jun 17, 2018 · 5 comments

Comments

@andywer
Copy link
Owner

andywer commented Jun 17, 2018

Extend react-dom to provide a generic, fully featured, declarative styling API that is as easy to use as inline styles, but comes with the power of CSS classes:

import React from 'react'

const MyButton = ({ children, primary }) => {
  return (
    <button css={{
      padding: '0 40px',
      background: primary ? '#ff7259' : '#00A1CB',
      border: 'none',
      border-radius: 3,
      color: 'rgba(255, 255, 255, 0.8)',
      ':hover': {
        color: 'rgba(255, 255, 255, 1)'
      }
    }}>{children}</button>
  )
}
export default MyButton

To render your app:

import * as ReactCSSDOM from 'react-css-dom'    // instead of react-dom
import MyButton from './Button'

ReactCSSDOM.render(
  <MyButton>Click me</MyButton>,
  document.getElementById('demo')
)

Server-side rendering should also be really simple. Something like that:

import * as ReactCSSDOM from 'react-css-dom'    // instead of react-dom
import MyButton from './Button'

const stylesheet = ReactCSSDOM.createStylesheet()
const rendered = ReactCSSDOM.renderToString(<MyButton>Click me</MyButton>, stylesheet)

const html = `
<!doctype html>
<html>
  <head>
    ${stylesheet.toStyleTag()}
  </head>
  <body>
    ${rendered}
  </body>
</html>
`

Benefits

  • Great for component libraries, since it requires no dependencies to be added to the library
  • Thus helps keeping the bundle small
  • Dead-simple to set up, even for SSR
  • Generic, universal styling API
@andywer
Copy link
Owner Author

andywer commented Jun 17, 2018

Difference between ReactDOM & ReactCSSDOM

  • there is a stylesheet instance keeping track of all the css classes the components yield
  • there is a map tracking which component uses which css class
  • on component mount:
    • turn css prop into a CSS class body
    • order props and normalize values
    • use hash (very simple, performant one) of class body as class name
    • add this css class to stylesheet instance
    • add a reference that this component uses this css class to the map
    • merge the css class' name into the component's className prop
  • on component unmount:
    • remove css class from component's className prop
    • remove reference that this component uses this css class from map
    • if there are no more components referencing this class, then remove class from stylesheet
  • on component update:
    • perform unmount and mount steps if the css prop changed
  • everything not related to the css prop stays untouched

Compatibility

  • components may still use a custom css prop for other purposes, since the ReactCSSDOM css prop is only interpreted on HTML elements, not on components

Implementation details

  • use react-reconciler
  • use react-dom host config to configure reconciler
  • adapt reconciler:
    • need to change ReactDOMFiberComponent.js
    • check out all occasions of the style prop handling and add css prop handling
    • might not need to edit the file at all, hopefully it's possible to just wrap its functions into functions that use the original ones, but also apply css prop handling

@stereobooster
Copy link

Question: how to generate @ rules like keyframes or font-face?

For inspiration:

@andywer
Copy link
Owner Author

andywer commented Jun 18, 2018

@stereobooster This is a good question indeed and I am not completely sure yet.

I have been thinking about using native „pseudo elements“, though:

const spin = React.createRef()

const Spinner = () => (
  <div css={{ animationName: spin, animationDuration: 2s }} />
)

ReactCSSDOM.render(
  <>
    <keyframes
      ref={spin}
      steps={{
        '0%': {
          transform: 'rotate(0deg)'
        },
        '100%': {
          transform: 'rotate(360deg)'
        }
      }}
    />
    <Spinner />
  </>
)

The example uses React 16.3‘s new createRef() API. Not sure if this would be the best way to go, though. Just spitballing here 😉

@giuseppeg
Copy link

nice! styled-jsx manages styles in a similar way https://github.com/zeit/styled-jsx/blob/master/src/stylesheet-registry.js

@andywer
Copy link
Owner Author

andywer commented Jun 25, 2018

@stereobooster I think this works better for at-rules:

const Spinner = () => (
  <keyframes
    steps={{
      '0%': {
        transform: 'rotate(0deg)'
      },
      '100%': {
        transform: 'rotate(360deg)'
      }
    }}
  >{spin => (
    <div css={{ animationName: spin, animationDuration: 2s }} />
  )}</keyframes>
)

It uses the children prop as render prop, very much like React 16.3's new context API does. Also this code snippet resembles a real-world use case better than the previous one.

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

No branches or pull requests

3 participants