emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.49k stars 1.11k forks source link

Emotion 10 #637

Closed emmatown closed 5 years ago

emmatown commented 6 years ago

Emotion 10 is going to introduce some new APIs for React users.

The main aim with these new APIs is to move all insertion to inside the React tree, this enables a couple of things:

The main new APIs are a custom createElement for the css prop and a Global component for global styles.

Example

The exact APIs are likely to change before Emotion 10 is released.

/** @jsx jsx */
import { jsx, Global } from '@emotion/core'
import css from '@emotion/css'
import { render } from 'react-dom'

render(
  <div>
    <h1
      css={css`
        font-size: 500px;
      `}
    >
      This is really big
    </h1>
    <Global
      css={{
        body: {
          backgroundColor: 'hotpink'
        }
      }}
    />
  </div>,
  document.getElementById('root')
)

If you want to try this, look at https://github.com/emotion-js/next but note that there will be breaking changes very often for now.

Another change that emotion@10 will bring is that styled.div and etc. will work without a babel plugin. The tag list will be included in @emotion/styled but if you use @emotion/styled.macro with babel-plugin-macros or the babel plugin, styled.div will be replaced with styled('div') and @emotion/styled-base will be imported instead which doesn't include the tag list.

Compatibility with Preact and non-React-like libraries

While the majority of Emotion users use React, there are some users who don't use React and we have to figure out how to cater for them.

Non-React-like libraries

Since all the packages on the @emotion scope are very modular, we can build the current emotion APIs with the packages on the @emotion scope. This should be pretty simple.

Preact

There are only a few APIs that we use that are new.

Fragment

Fragments are only used for SSR so we could have an alternative SSR API for that similar to extractCritical now.

createContext

We could tell people to use create-react-context and essentially polyfill createContext.

forwardRef

We could conditionally use forwardRef so if it's not available then we don't use it. I'm not a huge fan of this because I think it would be confusing to tell people they should use ref when using React and innerRef when using Preact but I'm not totally sure yet.

The other option to all of this is to not support the new APIs on Preact and have Preact users use the current emotion API.

Migration

This is something I'm still thinking about and I've got some ideas about but nothing concrete yet. I'm thinking of migrating emotion.sh and seeing what's difficult, how to make incremental migration easy, what can be done automatically with codemods and what warnings would be useful to provide.

Removing extractStatic

extractStatic hasn't been something that we've encouraged for a long time so @tkh44 and I think it's time to remove it. For people who like static extraction, libraries like Linaria and css-literal-loader do static extraction much better than Emotion does so those people should use those libraries.

Only do transformations when emotion is imported

babel-plugin-emotion currently transforms anything with the names of emotion's exports. While this works it transforms things for other libraries (#626 and #344) so we should only transform . I want to do this in Emotion 10 since it could be a breaking change for some people.

TODO

Emiliano-Bucci commented 6 years ago

That's odd but yes, it works! Thanks a lot :)

tkh44 commented 5 years ago

@Emiliano-Bucci, have you tried setting the jsx pragma in your babel config?

lifeiscontent commented 5 years ago

@mitchellhamilton in emotion 10, how would you handle the case where you have a base button and a theme button? https://gist.github.com/lifeiscontent/0c025a2fcb36cc9dc0848054411649c7

I'm really unsure what the refactor would look like here for theme button.

emmatown commented 5 years ago

@lifeiscontent https://gist.github.com/mitchellhamilton/af7cd8e25aac4bb3ed0871c1a6003223

lifeiscontent commented 5 years ago

@mitchellhamilton thanks a lot. Sorry, just 1 more question in the BaseButton, there is an option to have a disabledClassName, pressedClassName prop, how would you forward that if it was created in this case?

emmatown commented 5 years ago

I would probably use a child selector and target [disabled] and [aria-pressed] instead of the class names.

lifeiscontent commented 5 years ago

hmm ok, I guess I could do that.

lifeiscontent commented 5 years ago

@mitchellhamilton so with this refactor it looks like I can't use the component as a selector somewhere else.

e.g. css({[Button]: {...someStyle}});

how would I do that?

FezVrasta commented 5 years ago
css`
  ${Button} {
    color: red;
  }
`

is how it works with template literals, I'm not sure about object notation

lifeiscontent commented 5 years ago

@FezVrasta it doesn't work with either in this case, probably due to something special that happens when you wrap a component using styled.div, etc.

lifeiscontent commented 5 years ago

@mitchellhamilton how would you use useContext with a styled(Button)?

JesseChrestler commented 5 years ago

@mitchellhamilton does css in Emotion 10 become the same as a styled component at render time. From what i can see in the react tab it appears to be the case, can you confirm?

emmatown commented 5 years ago

does css in Emotion 10 become the same as a styled component at render time. From what i can see in the react tab it appears to be the case, can you confirm?

What do you mean by become the same as a styled component at render time?. They have different implementations if that's what you're asking.

how would you use useContext with a styled(Button)?

After hooks are stable and #967 is in, you'll be able to use hooks like useContext inside of interpolations.

JesseChrestler commented 5 years ago

@mitchellhamilton would Example1: const MyRedBox = <div css={css({backgroundColor:'red'})}></div> be the same as Example2: const MyRedBox = styled.div({backgroundColor:'red'}) I know the styles are obviously the same. From what i could tell it seemed like Example1 was wrapping itself around the <div> tag that it was assigned to. image

This must be part of the babel transforms that rewrite the output. This changes a lot of things and the expectation around the component and how they are being used. Maybe I'm wrong so i'm just trying to verify my thoughts. Thanks.

emmatown commented 5 years ago

It's wrapping the output but the examples are different, the first one is creating an element, the second is creating a component.

This changes a lot of things and the expectation around the component and how they are being used.

such as?

JesseChrestler commented 5 years ago

such as?

The css prop becomes magical higher order components that are attaching to the ref of your element to add a class name. It seems like a lot of work just to add a class name. Is this so css will work with react-native, and other devices that don't support style sheets? What are the advantages to this approach (vs v9)

emmatown commented 5 years ago

magical

Using magical to describe something is very unhelpful because it doesn’t really describe what’s wrong with something. Anything could be described as magical.

attaching to the ref of your element

We don’t use refs at all, refs are forwarded to the inner element.

What are the advantages to this approach (vs v9)

Also, something that’s important to note, we’re not getting of the old APIs, if you want to keep using them, you still can, you just won’t get the advantages of the new APIs.

lifeiscontent commented 5 years ago

@mitchellhamilton looks like there was an option to set displayName for a styled component in the babel plugin for emotion, how would you achieve the same thing in emotion@10? Is the babel plugin still required?

emmatown commented 5 years ago

Yes, the babel plugin is still required for labels(except with the css prop with function components). It's enabled by default in the v10 babel plugin.

nonewcode commented 5 years ago

Does this work with react-native and are there any performance concerns?

slikts commented 5 years ago

You can literally just write a random jsx; statement anywhere in the file. Linters hate it!

Is there a workaround to having to add jsx? I'm using CRA with TS and the @jsx pragma and the unused jsx statement make for a pretty ugly look. I suppose it's related to: https://github.com/babel/babel/issues/8958

tkh44 commented 5 years ago

Unfortunately, because CRA does not allow babel configuration, you cannot update the pragma via @emotion/babel-preset-css-prop.

jgoux commented 5 years ago

@slikts For CRA v1 you can use https://github.com/timarney/react-app-rewired to extend the config without ejecting. For the v2 there is https://github.com/sharegate/craco which I use and it works great! 👍 I'm not using TS so I'm not sure about the compatibility.

slikts commented 5 years ago

I added craco and babel-plugin-emotion and put this in craco.config.js:

module.exports = function({ env, paths }) {
  return {
    babel: {
      plugins: ["babel-plugin-emotion"],
    },
  };
};

Also: plugins: ["emotion"],

Then ran craco start, with no effect, --verbose just says craco: Added Babel plugins., and removing the jsx statement still causes ReferenceError: jsx is not defined.

I suppose there were reasons for these changes, but from my perspective emotion has taken a big step backward; it used to take just importing the template tag, but now I need to add 3 additional lines of noise: the babel pragma, the unused jsx import, and also an unused jsx statement.

jgoux commented 5 years ago

@slikts I don't think the @emotion/babel-preset-css-prop preset is included in babel-plugin-emotion. See here : https://emotion.sh/docs/css-prop#babel-preset

slikts commented 5 years ago

I installed @emotion/babel-preset-css-prop and added presets: ["@emotion/babel-preset-css-prop"], to craco.config.js, and now it claims to have applied the presets, but the only configuration that works is still with the 4 lines.

I liked emotion because it was minimal and elegant and allowed me to write CSS instead of object literals. Now it's Babel pragmas, unused imports, incompatibility with CRA/TS, and object literals in the main examples. It should have at least been released as -beta or -rc in the current state.

tkh44 commented 5 years ago

@slikts while I agree this takes more work, you can still use import { css } from 'emotion' or import styled from '@emotion/styled' just like you have been before. You do not have to use the css prop.

import styled from '@emotion/styled'

const Button = styled.button`
  color: turquoise;
`

Now it's Babel pragmas, unused imports, incompatibility with CRA/TS, and object literals in the main examples.

We've always supported both objects and strings, still do. I don't know where you got this idea.

slikts commented 5 years ago

I didn't say they're unsupported, just that the examples in the readme and elsewhere used to use template literals. It would be just:

import { css } from "emotion";

render(<div className={css`
  color: red;
`} />, root);

This was what I found elegant and why I used emotion; using CSS syntax, and having a clear model that, for example, you wouldn't have to explain to a colleague if they read your code. In contrast, the changes with pragmas and seemingly unused imports are hacky and fiddly. They're using a feature of Babel that I can't even find where it's documented, and I had to piece together what it is from a random blog post. The emotion readme just drops it there as if you'd be supposed to be familiar with it. Then, to actually use CSS syntax, I have to add an another import. I basically wouldn't even try to sell the new pattern to someone else who's possibly skeptical of CSS-in-JS as such, because it doesn't look good, requires an explanation, and even with an explanation it's like magic.

tkh44 commented 5 years ago

This was what I found elegant and why I used emotion; using CSS syntax, and having a clear model that, for example, you wouldn't have to explain to a colleague if they read your code.

Your example still works.

In contrast, the changes with pragmas and seemingly unused imports are hacky and fiddly. They're using a feature of Babel that I can't even find where it's documented, and I had to piece together what it is from a random blog post. The emotion readme just drops it there as if you'd be supposed to be familiar with it.

While it needs to be better documented, you do not have to use the css prop if you don't want it. https://emotion.sh/docs/css-prop is the only page in the documentation that shows object styles before string styles simply because that is what I prefer to use and it does not require and additional import. In order to use emotion you've always had to import the css function just as you showed in your example. Using objects with the css prop allows you to write styles without imports.

slikts commented 5 years ago

Your example still works.

It doesn't, the css tag function returns an object, and I'd have to know to use the vanilla package, which anyone familiarizing themselves with the library would probably miss and just see the css prop and Babel pragmas. The very first example in the readme also uses object literals.

akullpp commented 4 years ago

I basically wouldn't even try to sell the new pattern to someone else who's possibly skeptical of CSS-in-JS as such, because it doesn't look good, requires an explanation, and even with an explanation it's like magic.

This is a real issue, it is unacceptable to use the pragma as it seems hacky compared to styled-components. I won't introduce emotion because of this issue in a project...

Andarist commented 4 years ago

The good thing is that no one is forcing you to do that. You also have @emotion/styled API for grabs if you prefer using styled-components-like API.

We just don't believe that jsx pragma is a hack - you are free to feel to think differently.