-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
[RFC] Zero-runtime CSS-in-JS implementation #38137
Comments
I'm a bit concerned about how the theme is accessed. Right now, if |
@mwskwong We should use CSS vars. A lot faster and easier to write. We'll miss the typecheck, but we'll have to live with that. <Component sx={{ backgroundColor: 'primary' }} /> |
@mwskwong This is what you write in your code which will then be replaced at build time (dev or prod) with the generated css. |
Nice! I wonder how nested themes would be supported 🤔. Our use case: We heavily use nested themes where our main theme overwrites almost every component. For specific pages we have a unique look and feel where we use nested themes that partially update some components (ex font family for all h1-h4 or fully rounded buttons). Another use case: we have a fully dark based theme except for our ecommerce pages they are completely light theme based (again with a nested light theme), but the header and footer stay in the dark theme for example 😅. Looking forward! Regards |
hey, this looks amazing ! regarding Panda, I wanted to add a little more infos:
I can understand that. You could evaluate those
you could use the extractor on its own if that helps, it has no panda-specific dependencies ! |
This could be a brave idea, but what do you think about completely dropping or making opt-in CSS-IN-JS in favor of libraries like TailwindCSS or UnoCSS for styling? It could be an ambitious, but long-term win solution and definitely a win-win decision No matter what you will do, you will have to implement an intermediate layer. |
One question: Is CSS-in-JS support incoming for RSC on the React side? There has been some discussion around this, though I can't find where. |
The reason why runtime CSS-in-JS (not just CSS in JS in general to be exact) has so many problems in the new architecture of React is because as its name suggests, it needs JS in runtime to function, while RSC does the exact opposite. For this RFC, we are talking about zero runtime CSS-in-JS, so ideally, everything will be converted into plain CSS during built-time and won't have the issue we are facing. Although one thing to consider is whether it can maintain a certain degree of dynamic since we no longer have access to resources like theme during runtime. |
Why not use CSS Modules for theming ? I believe this would be simpler and easier to do. Though the dynamic styling part needs to be figured out. Maybe use good old styles object with We recently diteched our old DS which used context and CSS-IN-JS for theming and the new CSS Variables are way easier to do with Design tokens as well. Performant and can easily style components if required. We took alot of inspiration from MUI the way its built and the components API as well. Thanks for building this amazing Library. |
The situation I was mentioning can also appear to the new <List size="sm" sx={{ "--List-radius": theme => theme.vars.radius.sm }} /> Such a way of accessing the radius CSS var is more scalable. And yes, I can also do
Thanks @brijeshb42 on elaborate on that part, I would say that will be the best of both worlds. |
@JanStevens, we can support this by injecting CSS variables wherever we had previously nested |
Just wondering, with the introduction of |
@mwskwong We might still keep the |
@astahmer I did explore using |
Having no JS after compilation will be a major upgrade for MUI (YES! less JS shipped is always a good thing 👌), I hope you guys still support |
@SC0d3r Yes. We'll still have |
Going to just brain dump alot of thoughts about Linaria and Styled-Components after many many years of using them before going full circle back to SCSS for the last 1.5 years and never looking back. Linaria
Converting to/from linaria, styled-components/emotion and css modules is easier than people might think and because it's mostly based on patterns I'd say it would be possible to code-mod most of it or do an incremental approach 1 component at a time. Variables to/from
Modifers to/from
Loops to/from - Just use scss loops. === Final Thoughts === This may not be completely feasible with the MUI codebase but thought I'd share my long experience with Linaria to allow you to avoid potential headaches where possible. Our overall UI library and application architecture was much different than MUI with less configurability so while this was the best choice for us for the reasons above it may still be a good option for you. Will leave this here just to prove I was participating in Linaria community 4+ years ago |
Panda-css is amazing, I did some tests when I was looking for a lib that would solve the css-in-js problem. However it is in its first releases and I want to see what else they are planning but I can say that I trust the team at Chakra-ui a lot. |
Everything sounds great though i do have a major concern (or a question) here. Lets say i have a Design System library that wraps MUI, we don't have a build process other than passing our code via babel cli for both cjs and esm transpilations. My concern/question is, Do we now forced to use a bundler or any other tool or framework to be able to convert the styles in build time? The 2 possible solutions i see here, and i might be way off:
|
@sag1v We've already covered this in the RFC
|
seems like everything that is pure can be compiled away down to css classes, and everything that has some form of conditional or complex js needs to either remain with some small runtime or compile down to something that looks like a
So the strings/numbers become the important thing to interpret and then any complex code is given up on and left inline which leaves some runtime code but I don't know any way around it. The problem with inline code is always that it needs to be interpreted which is why vanilla extract just evals to css and leaves the dynamicism to their sprinkles js runtime. |
I was wondering... Instead of creating MUI's own zero-runtime CSS-in-JS solution, what about helping PandaCSS migrate from PostCSS to LightningCSS? MUI spends less time re-inventing the wheel and makes a great, existing CSS solution even better. |
@kylemh As explained in the RFC about panda, it's not about whether panda is using postcss or lightningcss to generate the css. Panda does not modify the original code. Which means whatever you write in your code along with how panda generates the classnames remain part of the final build and hence increase the overall bundle size together with its runtime. |
Interesting. Doesn't that mean their claim to zero runtime is incorrect? Also, another question... Will the solution MUI build be integrated as part of the monorepo or as a separate repository? If somebody wanted to use the CSS-in-JS library, will the relevant APIs be code-splittable? |
is there a guide for using MUI v6 for SSR? The current guide seems to be based on v5 (Emotion): |
This comment was marked as off-topic.
This comment was marked as off-topic.
I'm just some random guy who happened upon the thread, but... not a fan of sx, because it's only available on components from this library. love emotion's css prop, since it's available everywhere. Seeing your requirements, I'd suggest you do what emotion did and make your own jsx parser -- that way you could pick a syntax, abstract the underlying css lib, and it'd be available everywhere |
I'm not sure whether this solution will continue to include inline styles, but inline styles will become a blocker soon as Websites start deploying strict CSP and start using nonces/hashes within these policies. Can we please ensure inline styles are no longer included within MUI? See: #19938 |
@frattaro Since this is a build time solution, we have made |
@brijeshb42 would custom components then need to drill |
Honestly, I see this RFC as over engineering of existing solutions. CSS-in-JS DX not so good as it hyped. Regular scss modules much more flexible and not increase code of components. They allow to incapsulate styles from logic and make DX of reading code (which happens much more often than writing it) more pleasant. I suggest to think again about requirement to keep CSS-in-JS. With much less efforts you can migrate all components to scss modules and keep support of sx and styled stuff. |
@LeoVS09 that's a misapplication of architectures. components don't benefit from MVC as it results in spaghetti components (scrolling all over, switching files to find a class definition) when it's much more maintainable to keep concerns as colocated to their application as possible. Whether it be inline styles, atomic classes, css-in-js they all recognize that applying the styles directly to the component they affect is the desirable dx. |
The most important part of CSS-in-JS for me is the ability to reuse values between styles and JS, such as CSS variables' names, some CSS values for calculations in JS. It also results in simpler DX due to no need to learn Sass, Less, Stylus, etc.
The worst part of Pigment CSS is speed. For MacBook Pro M2 Pro or Max, it adds at least 5 seconds to the overhead to start a small project in Vite and ~10 seconds in a medium one. And I'm sorry for anyone who would try to run it in a big project. HMR mostly works, but sometimes (at least once a day) requires you to restart a Vite server
|
@o-alexandrov Did you happen to measure the build time speed increase? I'm curious.
What's the start time if you disable Pigment CSS in dev mode? I'm trying to get a sense of the % increase. If it's 1s and its get to 11s, it's a different story than if it's 30s and gets to 40s. In the first case, it feels like a deal breaker, that I can't use this for a project that will grow to 20+ engineers because everything will get incredibly slow in dev mode as the codebase grow. In the other case, it feels like it could be better, but I can make this tradeoff. |
@oliviertassinari in a medium size project, the In a [vite] Pre-transform error: ENOENT: no such file or directory, open './node_modules/.vite/deps/@mui_material.js'
Overall, I'm happy with Pigment CSS, if you want my opinion. It outputs CSS, resulting in a noticeable performance improvement comparing to
|
@o-alexandrov interesting, I think we need to keep a close eye on this build time performance. It seems that this will be one of the key challenges to overcome before Pigment CSS can reach mainstream adoption. For now, we are at about 1/1000 of the market depth: https://npm-stat.com/charts.html?package=%40pigment-css%2Freact&package=%40stylexjs%2Fstylex&from=2023-07-18&to=2024-07-18. Now, we will see, maybe it won't be that much of a blocker to grow x10 or x100. I suspect we will need to enforce more constraint into the API. For example, maybe we shouldn't support the import of values from other files, instead for developers to define those values once where they configure Pigment CSS. It would still be more flexible than Tailwind CSS or CSS Modules, and maybe this can unlock x10 faster build. |
@o-alexandrov @oliviertassinari I agree, at this stage of Pigment CSS, the perf is not that great and I feel like this is the next thing to improve before v1. cc @brijeshb42 |
@o-alexandrov I think stylex may be a better option for some use cases but I don't think it's a way to go for MUI (I know that this issue discussed about this but just want to double down my thought). The constraints are red flags that I won't use even in my side-projects. I think stylex might work for a very very big project that requires strict rule of how CSS is authored so that the projects don't go side way and leave legacy code to the next joiners. For Pigment CSS (in my opinion), the value is clear to me, we just want to solve the runtime bottle-neck of Material UI without changing the API surface. |
@o-alexandrov If it's not too much trouble, can you enable debug mode for Pigment CSS and share the output files. You can inspect the output files before sharing but it will mainly contain file paths and some timing information and won't have any sensitive information. const pigmentOptions = {
theme: yourTheme,
debug: {
dir: 'tmp',
print: true,
},
}; Then either zip and share the |
@brijeshb42 thank you, I added For
|
Just curious, what is the plan for Pigment CSS? Are you waiting for something specific in wyw-in-js (ex. their effort to explore oxc to address performance issues), realized relying on a 3rd party is more beneficial, or anything else? |
@o-alexandrov we are going to be focusing next on adding more Material UI integration examples with Pigment CSS, e.g. webpack integration and fixing bugs related to this. In general, we are going to be focusing on some APIs addition we want to have for v1, which we are going to share soon in an RFC. The next big thing would be improving the build performance - for this we still don't have a definite conclusion on how we are going to do, it could be trying to move some things to Rust, or maybe replacing wyw-in-js. We are also extending the team, looking for one more engineer to work on it: https://x.com/olivtassinari/status/1822981108255895619 Long term, for a future version of Material UI, we may look into bundling the styles for Material UI in the package - meaning we would use Pigment CSS in the build step of Material UI, so we don't as developers to transpile the code coming from node_modules. These are some high-level updates, we are going to be sharing them in the public roadmap for Pigment CSS by the end of the year. Let me know if you have more questions :) |
@mnajdova please refer to this comment by @Anber
I totally agree performance should be handled last; i.e. after fixing bugs that prevent to use PigmentCSS w/ MaterialUI and missing features.
Main bugs, imho: #43750, #43722, there's no open issue for HMR that breaks very often, but it's hard to create a reproduction for it; if you use PigmentCSS in any small project, you would stumble on it |
It would be helpfull if there would be an example of using Emotion and Pigment together in NextJs. If we use Pigment in the main layout and pages layouts it would unlock already features we can use with Server Components. So I assume we need to have the Pigment setup in the layouts and then ThemeProvider for emotion wrapping individual pages. Is this a recommended setup for this? Will it work good together, any pitfalls? |
What's the problem? 🤔
This RFC is a proposal for implementing a zero-runtime CSS-in-JS solution to be used in a future major version of Material UI and Joy UI.
TLDR: We are planning to develop a custom implementation of a zero-runtime CSS-in-JS library with ideas from Linaria and Compiled.
With the rising popularity of React Server Components (RSCs), it’s important that we support this new pattern for all components that are compatible. This mainly applies to layout components such as Box, Typography, etc., as they are mostly structural and the only blocker for RSC compatibility is the use of Emotion.
Another aspect is the use of themes. Currently, they need to be passed through a Provider component (especially if an application is using multiple themes) which uses React Context. RSCs do not support states/contexts.
In the last major version, we moved the styling solution to Emotion for more performant dynamic styles. Since then, Internet Explorer has been deprecated, enabling us to go all in on CSS Variables. We already use this with an optional provider (CSS theme variables - MUI System).
What are the requirements? ❓
sx
prop along with container-specific props like<Box marginTop={1} />
etc.emotion
as well asstitches
, as mentioned below.What are our options? 💡
We went through some of the existing zero-runtime solutions to see if they satisfy the above requirements.
.css.ts
file felt like a negative point in DX.sx
prop led us to pass on Linaria.styled
function, Box props, and an equivalent of thesx
prop. The major drawback, however, is that this is a PostCSS plugin, which means that it does not modify the source code in place, so you still end up with a not-so-small runtime (generated usingpanda codegen
) depending on the number of features you are using. Although we can’t directly use PandaCSS, we did find that it uses some cool libraries, such asts-morph
andts-evaluate
to parse and evaluate the CSS in its extractor package.styled()
API that we know and love. This would be the least preferred option for Material UI, especially given the way our components have been authored so far.Although we initially passed on Linaria, on further code inspection, it came out as a probable winner because of its concept of external tag processors. If we were to provide our own tag processors, we would be able to support CSS object syntax as well as use any runtime CSS-in-JS library to generate the actual CSS. So we explored further and came up with two implementations:
styled
API from Stitches. See this discussion for the final result of the exploration.The main blocker for using Linaria is that it does not directly parse the JSX props that we absolutely need for minimal breaking changes. That meant no direct CSS props like
<Box marginTop={1} />
orsx
props unless we converted it to be something like<Component sx={sx({ color: 'red', marginTop: 1 })} />
. (Note the use of ansx
function as well.) This would enable us to transform this to<Component sx="random-class" />
at build-time, at the expense of a slightly degraded DX.Proposed solution 🟢
So far, we have arrived at the conclusion that a combination of
compiled
andlinaria
should allow us to replacestyled
calls as well as thesx
and other props on components at build time. So we’ll probably derive ideas from both libraries and combine them to produce a combination of packages to extract AST nodes and generate the final CSS per file. We’ll also provide a way to configure prominent build tools (notably Next.js and Vite initially) to support it.Theming
Instead of being part of the runtime, themes will move to the config declaration and will be passed to the
styled
orcss
function calls. We’ll be able to support the same theme structure that you know created usingcreateTheme
from@mui/material
.To access theme(s) in your code, you can follow the callback signature of the
styled
API or thesx
prop:Although theme tokens’ structure and usage won’t change, one breaking change here would be with the
component
key. The structure would be the same, except the values will need to be serializable.Right now, you could use something like:
But with themes moving to build-time config,
onClick
won’t be able to be transferred to the Button prop as it’s not serializable. Also, a change in thestyleOverrides
key would be required not to useownerState
or any other prop values. Instead, you can rely on thevariants
key to generate and apply variant-specific stylesBefore
After
Proposed API
The
styled
API will continue to be the same and support both CSS objects as well as tagged template literals. However, thetheme
object will only be available through the callback signature, instead of being imported from a local module or from@mui/material
:The
theme
object above is passed through the bundler config. At build-time, this component would be transformed to something like that below (tentative):Dynamic styles that depend on the component props will be provided using CSS variables with a similar callback signature. The underlying component needs to be able to accept both
className
andstyle
props:Other top-level APIs would be:
css
to generate CSS classes outside of a component,globalCss
to generate and add global styles. You could also directly use CSS files as most of the modern bundlers support it, instead of usingglobalCss
.keyframes
to generate scoped keyframe names to be used in animations.Alternative implementation
An alternative, having no breaking changes and allowing for easy migration to the next major version of
@mui/material
is to have an opt-in config package, say, for example,@mui/styled-vite
or@mui/styled-next
. If users don’t use these packages in their bundler, then they’ll continue to use the Emotion-based Material UI that still won’t support RSC. But if they add this config to their bundler, their code will be parsed and, wherever possible, transformed at build time. Any static CSS will be extracted with reference to the CSS class names in the JS bundles. An example config change for Vite could look like this:Known downsides of the first proposal
Material UI will no longer be a
just install-and-use
library: This is one of the features of Material UI right now. But with the changing landscape, we need to compromise on this. Several other component libraries follow a similar approach.Depending on the bundler being used, you’ll need to modify the build config(
next.config.js
for Next.js,vite.config.ts
for Vite, etc.) to support this. What we can do is provide an abstraction so that the changes you need to add to the config are minimal.Resources and benchmarks 🔗
Playground apps -
Related issue(s)
The text was updated successfully, but these errors were encountered: