Closed emmatown closed 5 years ago
This looks great. I like this API quite a bit. Are there any downsides that we should address?
The main downside is that it'll be hard to get class names outside of render if you're using the new APIs, that's intentional though. Another smaller thing that I haven't really figured out yet is keyframes
, currently in the new API, you use it like this
/** @jsx jsx */
import jsx from '@emotion/jsx'
import keyframes from '@emotion/keyframes'
import css from '@emotion/css'
const animation = keyframes(css`
from {
color: green;
}
to {
color: hotpink;
}
`)
const Comp = () => (
<div
css={[
{
animation: `1s ${animation.name}`
},
animation.styles
]}
/>
)
which is pretty inconvenient since you have to pass in the name and the styles, for string styles we could interpolate the animation object and insert the animation but for object styles we can't do that since we only have a string when we interpolate the object. Any thoughts on how we could solve this?
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, styled.div will be replaced with styled('div') and @emotion/styled-base will be imported instead which doesn't include the tag list.
What is the argument behind this? IMHO it brings unnecessary config right from the start when one wants to use emotion
in production (assuming everyone should do it as it's less bytes without the tag list).
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.
IMHO consistency is better, so I'd vote for keeping innerRef
. In the meantime it might be good to research what preact's plans are about those new React APIs.
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.
Even if the feature is not actively developed and in non-ideal state, I don't quite see why should it be removed. It surely has some audience, brings some benefits to the users and having it might encourage outside contributors to make it cover more cases.
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 but if you use .macro, styled.div will be replaced with styled('div') and will be imported instead which doesn't include the tag list.
What is the argument behind this? IMHO it brings unnecessary config right from the start when one wants to use emotion in production (assuming everyone should do it as it's less bytes without the tag list).
I'm not sure if you're referring to using a babel macro or including the tag list by default so I'll talk about both
In terms of doing the optimization with a babel macro, it was mainly because macros will be supported in react-scripts@2 and because the API made the optimization really easy. I think it would be a good idea to do it with a babel plugin as well. What I'd like to do though is make a util that accepts a certain import source and a function that transforms it with the same API as a babel macro so we can reuse all the logic and we don't have problems like #344 and #626
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.
Even if the feature is not actively developed and in non-ideal state, I don't quite see why should it be removed. It surely has some audience, brings some benefits to the users and having it might encourage outside contributors to make it cover more cases.
It requires some runtime code for styled so removing it would mean styled would be slightly smaller. (I know the amount of code it uses isn't very big, it still helps though) While extractStatic may have some small audience, other libraries do a better job of it so there's no reason people should use emotion over those other libraries. Also, explaining it in the docs is hard because we have to essentially say "we have this thing, it doesn't work very well, we don't recommend it and we're not planning on improving it but it's still here".
If we did keep it and contributors improved it, it would still likely require you to follow certain rules to make it statically analyzable which would limit the power of css-in-js.
If we did keep it and contributors improved it, it would still likely require you to follow certain rules to make it statically analyzable which would limit the power of css-in-js.
That is true, emotion
's (and other css-in-js) true power lies in dynamic per props & theme styling, but at the same time not everything is dynamic in styles and that static part can be extracted. Keeping it would allow perfecting algorithm some day and would combine benefits of static extraction & dynamic styling at the same time. Just my 2 cents though π
Never liked injectGlobal
api in any css-in-js library.
It's a great idea to move away from globals.
Especially with React Suspense and easy streaming on server side globals will be the most annoying thing ever.
As for babel-plugin, i was avoiding trying next
version before you added it. Thank you.
With glamorous deprecation the main 2 css-in-js libraries for me are Styled Components and Emotion :+1:
Really excited about the direction of this. Had a couple of questions while thinking about what I might need to update.
If the css prop will be the favored approach do you envision most components having a bunch of outer scope variables for these styles? I really enjoy the css prop pattern, but it messes with the readability of a complexly styled components. This will somewhat involve class naming for the variables. e.g.
const borderedBox = css`<styles>`;
const Box = props => <div css={borderedBox} ...<lots of props>><a bunch of nested components></div>
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, styled.div will be replaced with styled('div') and @emotion/styled-base will be imported instead which doesn't include the tag list.
Does this means for optimal perf we'll need to use styled('div')
or
if(isProd) {
import(@emotion/styled.macro)
} else {
import(@emotion/styled)
}
styled.div
The css prop works without the babel plugin because it's implemented as a custom createElement
Does this mean doubling the number of dom elements or is it just a React implementation of the element?
If the css prop will be the favored approach do you envision most components having a bunch of outer scope variables for these styles? I really enjoy the css prop pattern, but it messes with the readability of a complexly styled components. This will somewhat involve class naming for the variables. e.g.
Could you give an example of what you would write now for that case?
Does this means for optimal perf we'll need to use styled('div') or
It only means you'll always be able to use styled.div
. If you use @emotion/styled
without the babel plugin, you'll include the tag list. If you use @emotion/styled.macro
which requires babel-plugin-macros which will be included in create-react-app 2 or you use the babel plugin, styled.div
will be converted to styled('div')
and @emotion/styled-base
will be imported which doesn't include the tag list.
Does this mean doubling the number of dom elements or is it just a React implementation of the element?
I'm not sure if I totally understand what you mean but the only time when there will be more dom elements is when doing SSR and style elements get rendered. You can look at how it works here
Here's a meaty one:
const primaryStyle = css`
background: ${colorMap.GreenDark};
border: 1px solid ${colorMap.GreenDark};
color: #fff;
font-size: ${size.default};
&:hover {
background: #507b32;
border-color: ${colorMap.GreenDark};
color: #fff;
}
&:active {
${primaryActiveStyle};
}
&[disabled],
&[disabled]:hover {
background-color: ${colorMap.slateNeutral};
border: 1px solid #e9ebec;
box-shadow: -2px 4px 2px -4px ${colorMap.slateLight};
color: ${colorMap.slateLight};
}
`;
It only means you'll always be able to use styled.div.
Ok, so nothing changes if we're using the babel plugin already. π My concern was that it would be more performant in dev to use the whitelist, but fewer bytes over the wire with the macro (hence a different import depending on env.)
I'm not sure if I totally understand what you mean but the only time when there will be more dom elements is when doing SSR Add a dev warning when :first-child and selectors like it are used and recommend replacements because of problems when rendering style elements in SSR and those selectors targeting the style elements.
Cool, then this won't affect my project atm, but my concern was adding another dom node for every css prop used on the page. For large pages I'd imagine this could add overhead.
Losing :first-child
is going to be a little confusing for some of my junior devs when we start server-rending. I wonder if the babel plugin could change :first-child
to :first-of-type
or first-child:not(<customElement class>)
?
Here's a meaty one:
Sorry, I meant one that would be different with the css prop in regards to this comment. As in, how would the readability change from how it is now and how do you have to name more than in the past?
If the css prop will be the favored approach do you envision most components having a bunch of outer scope variables for these styles? I really enjoy the css prop pattern, but it messes with the readability of a complexly styled components. This will somewhat involve class naming for the variables. e.g.
Losing :first-child is going to be a little confusing for some of my junior devs when we start server-rending. I wonder if the babel plugin could change :first-child to :first-of-type or first-child:not(
)?
The reason that it isn't done automatically is that if we did it in the babel plugin there would be cases where it wouldn't work and if we did it at runtime it would mean checking every single selector which would be expensive. Also, there's no direct replacement for :first-child that ignores style elements.
Most of the examples and docs so far use inline styles for the css prop. It seems like the goal is to move toward that and away from styled
. If that's not true than nothing is really different, I was thinking of new users who would be writing long strings of styles within a component and not knowing there are alternatives.
I thought this might work $('div :first-child:not(style)')
, but realized you can't actually select style
π€¦ββοΈ
Hey guys! Quick question: Wouldnβt it be cleaner to have a customized react-dom
with a reconciler that handles the css
prop natively instead of patching createElement
?
Just a thought π Wrote down some ideas about it here when Twitter folks mentioned this issue.
Btw, great stuff, though π
@andywer Thank you for your opinion! However, as far as I think, it will make emotion too strongly depending on reconciling mechanism of react. The element structure of react is not that big stuff, but reconciling mechanism, especially after React@16, is quite big and complex one...
@andywer Just to be clear, we're not patching createElement, we're providing a function that you explicitly use instead of createElement.
IMO, creating a custom reconciler would be so much more complex, require a lot of work(and maintenance over time since it would have to be changed every time that react-dom was changed) and wouldn't really have any significant advantages over a custom createElement. Also, what do you mean by "cleaner" and why is having a custom reconciler "cleaner"?
Also, having a custom reconciler would make it harder to share components since you would have to make people use the custom reconciler as well rather than just exporting a component and using it with react-dom. This is especially important because one of the goals with this was to make it possible to create components with styles that can be imported from npm and just work without any configuration including with server-side rendering.
Add an alternative SSR API to get around the caveats with rendering style elements
Can you point me in the direction of what you mean by this?
I've done some experimenting with https://github.com/emotion-js/next and its worked an absolute treat. Feels really clean/simple, and as you said, SSR just works since the style tags come out as part of the standard rendered react component tree.
One good argument in favour of using things like the extractStatic
option or Linaria (i.e. extracting style information into css files) is that it avoids the needs to send style information twice on the initial render (Once before/in the server rendered HTML to ensure that the page renders correctly the first time, and once again as part of the javascript so that it can continue to keep the styles up to date after React mounts). With https://github.com/faceyspacey/extract-css-chunks-webpack-plugin and associated packages you can avoid that duplication as long as you put the styles into CSS files somewhere in your build pipeline.
Is that something that is or could be addressed with this new approach of rendering style tags within react? This has got me thinking though - already with SSR you duplicate the HTML (the SSR HTML version and the javascript/React version). I guess this is just an extension of that.
@hedgepigdaniel The caveat I'm referring to is that :first-child
and pseudo classes like it are unsafe because the style elements are rendered next to other elements so the pseudo classes could target the style elements instead of the element they're intended to be targeting.
The new approach doesn't change anything with regards to static extraction. I'm not very interested in static extraction right now, I personally find the trade offs not worth the benefits. I think that the only practical way to do static extraction without big trade offs is with something like prepack.
Ah cool. Since the box is checked... where is the alternative SSR API?
If I do ReactDom.renderToString(<App />)
with the latest @emotion/core
, the style tags are throughout the DOM tree in the rendered HTML, not in <head />
. It sounds like this new approach is a way of SSRing the app such that the styles are all in the <head />
.
@hedgepigdaniel the alternative api is using the current emotion ssr apis with the compat cache.
You can see an example of it in the tests, https://github.com/emotion-js/emotion/blob/master/next-packages/compat-cache/__tests__/server.js
How's going to work the custom createElement
with CRA?
Do they support the /** @jsx jsx */
comment and we are going to use it on every single file, or there is some way to change that setting globally?
I highly doubt they disallow jsx pragma, so it should "just work" with the mentioned /** @jsx jsx */
comment. If you want to change it globally you'd have to use smth like creat-react-app-rewired.
Is there a way to disable pragma with babel-plugin-transform-react-jsx
? As far as I know, there is no such way...
anyone know if innerRef has been converted to ref in this branch? It's the only thing that is stopping me from using emotion and the new ThemeProvider is awesome! Looks like I'd be able to hook directly into it, and override some functionality that I need
Yes, using ref is supported in v10.
@mitchellhamilton any idea on when v10 might be released?
Itβs in beta, you can see the docs at https://next.emotion.sh
@mitchellhamilton can I install the beta with yarn add emotion@next
?
Read the instructions at https://next.emotion.sh/docs/migrating-to-emotion-10, all packages not on the emotion npm scope + the react native packages are available on the next tag on npm, all the new packages are on the latest tag.
@mitchellhamilton I'm looking for an API that works similar to this.
base/Button.js
const Button = ({
as,
pressed,
className,
disabledClassName,
pressedClassName,
...props
}) =>
React.createElement(as, {
className: cx(className, {
[disabledClassName]: props.disabled,
[pressedClassName]: pressed,
}),
'aria-pressed': pressed,
'aria-disabled': props.disabled,
...props,
});
Button.defaultProps = {
as: 'button',
className: 'Button',
disabledClassName: 'Button--is-disabled',
pressedClassName: 'Button--is-pressed',
};
Button.propTypes = {
as: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
className: PropTypes.string,
disabled: PropTypes.bool,
disabledClassName: PropTypes.string,
pressed: PropTypes.bool,
pressedClassName: PropTypes.string,
};
export { Buton };
theme/Button.js
import { Button as BaseButton } from "../base/Button";
const Button = styled(Button)(props => ({className: css(...), pressedClassName: css(...)}));
export { Button };
do you have a recommended way on how to achieve something like this in emotion 10? ideally also having the ability to include variant button styles.
@mitchellhamilton Awesome changes! Really looking forward to v10. Is it still intended to include a <ScopeProvider/>
to help with CSS prioritization and interoperability with other styling techniques/libraries? I see that there is a module called @emotion/provider
from the docs. Thank you!
@mitchellhamilton I found <ClassNames>
but it looks like its not properly passing the theme that is injected from the ThemeProvider.
@mitchellhamilton Awesome changes! Really looking forward to v10. Is it still intended to include a
to help with CSS prioritization and interoperability with other styling techniques/libraries? I see that there is a module called @emotion/provider from the docs. Thank you!
We're not planning on including something like ScopeProvider natively but it's possible to build it based on the CodeSandbox I showed. Where did you see @emotion/provider
in the docs? It shouldn't be used.
@mitchellhamilton I found
but it looks like its not properly passing the theme that is injected from the ThemeProvider.
Are you using the ThemeProvider
from emotion-theming@next
?
We're not planning on including something like ScopeProvider natively but it's possible to build it based on the CodeSandbox I showed. Where did you see @emotion/provider in the docs? It shouldn't be used.
@mitchellhamilton Ok, got it. Thanks for the clarification. In order to work with other styling libraries or deal with CSS prioritization, are the traditional methods the best route forward? For example, wrapping an already externally styled component like so styled(ComponentWithJSS)
or do you recommend something like the <ScopeProvider />
?
Looks like you caught the import mistake: https://github.com/emotion-js/emotion/commit/56581cbb56c4e3386211c740719dfe40095141b9#diff-12703cf35278a283eae498a16e376670. Thanks for updating.
In order to work with other styling libraries or deal with CSS prioritization, are the traditional methods the best route forward? For example, wrapping an already externally styled component like so styled(ComponentWithJSS) or do you recommend something like the
?
if styled(ComponentWithJSS)
works, keep using it.
@mitchellhamilton good point, I'll check on that and report back π
@mitchellhamilton how would you test a component with a theme using the ClassNames component?
just wrap each component with a provider? or can you some how specify a default theme?
Yes, wrap it in a ThemeProvider.
Hi @mitchellhamilton , I'm giving emotion 10 a try today and I have a few questions :
Is it planned to add automatic insertion of /** @jsx jsx */
with babel-plugin-emotion
? (I saw the mention Alternatively, use this TODO babel preset
in the migration guide)
Is ClassNames
necessary for passing a className to a component? If we use babel-plugin-emotion
, is it possible to use this syntax instead
import { css } from '@emotion/core'
// I already try that, it returns an object where the name key seems to be the className?
const activeClassName = csscolor: blue;
- What's the exact purpose of the exported `css` from `@emotion/core`? Is there any access to the props when using `css` ?
So far it seems promising! βοΈ
Is it planned to add automatic insertion of /* @jsx jsx / with babel-plugin-emotion? (I saw the mention Alternatively, use this TODO babel preset in the migration guide)
You can use eslint-plugin-emotion which will add it.
Is ClassNames necessary for passing a className to a component?
In general, you should use the css prop, ClassNames should rarely if ever be used.
If we use babel-plugin-emotion, is it possible to use this syntax instead
No.
What's the exact purpose of the exported css from @emotion/core?
Syntax highlighting, performance, allowing interpolations, you pass the result of the css
to the css prop.
Is there any access to the props when using css?
No.
You can use eslint-plugin-emotion which will add it.
I was more looking for a compile step than a codemod. Ideally I'd like the pragma to be absent from my code. For example this plugin (https://github.com/satya164/babel-plugin-css-prop) looks for a css
prop in JSX declaration so I think it would be possible to rewrite the pragma if the css
prop is used?
In general, you should use the css prop, ClassNames should rarely if ever be used.
My use case is the activeClassName
prop from react-router Link
component. I don't really understand why a render-prop is necessary to get the className with css
.
Thanks for your answers! π
EDIT : It seems that there is already a plugin for injecting the source of the pragma ! https://github.com/WordPress/gutenberg/tree/master/packages/babel-plugin-import-jsx-pragma
I'm not sure about the performance if we use it for every JSX declaration instead of checking for the presence of the css
prop first.
EDIT II : create-react-app makes it difficult to add a new pragma globally. There is an issue about it : https://github.com/facebook/create-react-app/issues/5152
I have a similar need to regularly pass a className to a component from a third party library (in my case, Ant Design, https://ant.design/, which many of my UI components wrap and add additional styles to). I can make the ClassNames render-prop work, but having a lighter-weight solution would be nice...
My use case is the activeClassName prop from react-router Link component. I don't really understand why a render-prop is necessary to get the className with css.
It's necessary to allow the out of the box server rendering and to access context so things like iframes and providing options can work via context
With adding the pragma globally, I don't really understand why people find it so important? It takes two lines to import and set the pragma and for that, it makes it easier for people who may not have used emotion or the css prop to understand why the css prop gets converted into a className since they can see that the jsx pragma has changed, look at emotion from that and etc.
It's necessary to allow the out of the box server rendering and to access context so things like iframes and providing options can work via context
Ok, I can live with it. Thanks again for explaining me the tradeoffs. π
With adding the pragma globally, I don't really understand why people find it so important? It takes two lines to import and set the pragma
As a developer I'm lazy and if I can automate a thing and type less then I always go that road. We should at least have the possibility to do it even if it's not officially advertised.
it makes it easier for people who may not have used emotion or the css prop to understand why the css prop gets converted into a className since they can see that the jsx pragma has changed, look at emotion from that and etc.
With the current ecosystem and the heavy usage of compilers such as Babel, I find it more understandable to rely on a plugin to transform my code globally (when the plugins usage are well explained). It also reduce the technical noise in my codebases, every line not written is a win, especially when these lines are about implementation details and can be automated. If https://github.com/WordPress/gutenberg/tree/master/packages/babel-plugin-import-jsx-pragma exists for something as huge as wordpress then maybe there is a use case for it.
This is all my own opinion of course! π
We should at least have the possibility to do it even if it's not officially advertised.
Yep, I agree, and it's totally possible, you can use the babel plugin you mentioned, the emotion site even uses a similar plugin. We may even create a babel preset to do that but the one thing that I'm afraid of is that I don't want to create too many choices for people when they want to use emotion so if we add babel preset it likely won't be the primary way we intend for people to use it since importing it and setting the jsx pragma works everywhere whereas adding a preset only works when you have access to the babel config.
For now I gave a possible userland solution here for anyone interested.
Thanks for the answer @mitchellhamilton , and amazing job on emotion 10! π
One last question, does a styled component requires the custom pragma?
For example with the future as
property, if I define a Box
primitive like this :
import styled from '@emotion/styled'
import { AppBar } from '@material-ui/core'
const Box = styled.div``
const App = () => (
<Box as="main" css={css`width: 100vw; height: 100vh;`}>
<Box as={AppBar} css={css`font-size: 4em;`}></Box>
</Box>
)
does it requires the pragma transformation or is the css
prop already built-in into the styled component?
Hi to all, i'm encountering some issues using the new version of Emotion with next.js and typescript. The fact is that every time i use
/** @jsx jsx */ import { jsx } from "@emotion/core";
i always have the same error: ReferenceError: jsx is not defined
I've tried to made a fresh install (without typescript) and works fine, so maybe is an issue with typescript? Or a babel next.js config? Does anyone had the same problem?
Thanks a lot :)
@Kovensky Sorry but, what do you mean by
Adding an unused jsx reference anywhere to the file makes the build correct
You can literally just write a random jsx;
statement anywhere in the file. Linters hate it!
But it works...
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:
createElement
(this isn't necessarily a result of moving things inside the React tree but a side-effect of the new implementation)The main new APIs are a custom
createElement
for the css prop and aGlobal
component for global styles.Example
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 withstyled('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 polyfillcreateContext
.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 andinnerRef
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
@emotion
keyframes
API more convenient@emotion/styled.macro
:first-child
and selectors like it are used and recommend replacements because of problems when rendering style elements in SSR and those selectors targeting the style elements.extractStatic
in babel-plugin-emotion and add a notice to the docs (if anyone wants to submit a PR for this, that would be great!!)extractStatic