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

[pigment-css] Add stringifyTheme for Pigment CSS integration #42476

Merged
merged 20 commits into from
Jun 12, 2024

Conversation

siriwatknp
Copy link
Member

@siriwatknp siriwatknp commented Jun 1, 2024

blocked by mui/pigment-css#124

closes mui/pigment-css#130

For mui/pigment-css#105

To make the serializable theme available at runtime because some components are accessing runtime theme with useTheme.

Usage with Pigment CSS:

// next.config.js
const { extendTheme, stringifyTheme } = require('@mui/material/styles');

const theme = extendTheme();

theme.toRuntimeSource = stringifyTheme;

module.exports = withPigment({
  theme,
})

@siriwatknp siriwatknp added package: material-ui Specific to @mui/material v6.x customization: theme Centered around the theming features labels Jun 1, 2024
@mui-bot
Copy link

mui-bot commented Jun 1, 2024

Netlify deploy preview

https://deploy-preview-42476--material-ui.netlify.app/

@material-ui/core: parsed: +0.13% , gzip: +0.17%
SwipeableDrawer: parsed: +0.05% , gzip: +0.35%

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against 6524424

import { stringifyTheme } from './stringifyTheme';
import extendTheme from './extendTheme';

describe('StringifyTheme', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
describe('StringifyTheme', () => {
describe('stringifyTheme', () => {

@siriwatknp siriwatknp added the on hold There is a blocker, we need to wait label Jun 5, 2024
Comment on lines +37 to +49
function serializeTheme(object: Record<string, any>) {
const array = Object.entries(object);
// eslint-disable-next-line no-plusplus
for (let index = 0; index < array.length; index++) {
const [key, value] = array[index];
if (!isSerializable(value) || key.startsWith('unstable_')) {
delete object[key];
} else if (isPlainObject(value)) {
object[key] = { ...value };
serializeTheme(object[key]);
}
}
}
Copy link
Member

@oliviertassinari oliviertassinari Jun 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to remove all the CSS variables values: colors, typography, etc., so it breaks at runtime if developers try to access them. For example theme.color.primary.main would be wrong as soon as the theme changes. So if it's impossible to access, we would avoid a footgun.

Then for these dynamic values (have CSS variables), solve it with: https://github.com/mui/pigment-css/issues/129.

But honestly, the theme replacement at build-time feels wrong in the first place. I don't think we should invest more time into it. From what I understand, we are only doing it for RSC, but it feels like the worst technical solution available if there is a working React.cache() alternative: either like yak is doing it, or like this: emotion-js/emotion#2978 (comment).

Copy link
Member Author

@siriwatknp siriwatknp Jun 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how would you make it work for components that are using useTheme() at runtime, for example, Material UI Tooltip?

At this point, I think we need it (can be an internal API) otherwise v6 will be delayed for at least a month.

From what I understand, we are only doing it for RSC

No, we are doing this because some components are accessing the theme at runtime (those that call useTheme().

but it feels like the worst technical solution available if there is a working React.cache() alternative

Again, I think I mentioned in some issue to you that React.cache is still experimental. The options we have are:

  1. Use wyw-in-js to provide runtime theme (small effort)
  2. Go with your proposal, could be small or large effort because we don't know the edge cases and React.cache is still experimental.

To me, both options give almost the same result, (1) is a bit better because there is no useContext involved compare to option (2). That's why I would go with option (1) and keep the API internal to launch v6 and then when the time is right, switch to option (2) if necessary.

What's your opinion? cc @mnajdova @brijeshb42

Copy link
Member

@oliviertassinari oliviertassinari Jun 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.cache is still experimental

https://nextjs.org/docs/app/building-your-application/caching#react-cache-function doesn't make it so clear.

But how would you make it work for components that are using useTheme() at runtime, for example, Material UI Tooltip?

The way I see it:

For the server bundle, you have a full duplicate of the theme object, you can import the same one that the plugin uses. This theme is made available with the <CssVarProvider> which injects the theme for the children's server components that were not extracted at build time. The theme is propagated ideally with the React.cache() API because it has no bundler integration needs or a global window value (maybe with some capabilities to have multiple versions of Pigment CSS run in the same app)
Then, the children use useTheme(), but this module must not be flagged as a client bundle, so we remove "use client" to have it as a shared moduie: https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#conventions-for-serverclient-components. That would work like this: emotion-js/emotion#2978 (comment)
A theme replacement during the build could be an option but it sounds more work and depends more on the bundler.

For the client bundle, you have the whole theme available, you can import the same one that the plugin uses. We make it propagate in the application with the React Context API, from <CssVarProvider> who injects it with a child. I mean, the CssVarProvider stays a server component but uses a client component inside himself using https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props. The challenge there is that for this to work, the server effectively needs to serialize the theme https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#passing-props-from-server-to-client-components-serialization, which breaks all its functions.

One way to solve this is to use Yak a plugin that I assume will somehow make this theme available in the client component (see how they use client hints https://yak.js.org/features#theming to create the theme).

Another, I don't know if it works, is to have both a client and a server component imports that theme, and inject it, so it's never serialized like in https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#passing-props-from-server-to-client-components-serialization.

Personally, I see no need to serialize/stringify a theme with this model.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@siriwatknp siriwatknp mentioned this pull request Jun 10, 2024
1 task
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jun 11, 2024
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jun 11, 2024
@siriwatknp siriwatknp removed the on hold There is a blocker, we need to wait label Jun 12, 2024
@siriwatknp
Copy link
Member Author

@brijeshb42 the build passes, I believe that it's ready for the final review.

@@ -9,7 +9,7 @@
"clean": "rimraf .next"
},
"dependencies": {
"@pigment-css/react": "^0.0.12",
"@pigment-css/react": "^0.0.13",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Member Author

@siriwatknp siriwatknp Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest for @pigment/react is 0.0.13. Only @pigment-css/nextjs-plugin that's get updated in 0.0.14.

Is that what you mean?

const theme = ${JSON.stringify(serializableTheme, null, 2)};

theme.breakpoints = createBreakpoints(theme.breakpoints || {});
theme.transitions = createTransitions(theme.transitions || {});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also found that we'll have to have theme.shadows for Paper. But I see that it uses shadows in non production env. Have you tried running the demo apps in dev mode and see if it works ?

Copy link
Member Author

@siriwatknp siriwatknp Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it works. The theme.shadows are in serializableTheme in line 56.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

This proves that the runtime theme works!

Copy link
Contributor

@brijeshb42 brijeshb42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Great work on this.

@pushys
Copy link
Contributor

pushys commented Jul 22, 2024

stringifyTheme doesn't seem to be exported from @mui/material/styles anymore, although it is still mentioned in the docs.

@siriwatknp
Copy link
Member Author

stringifyTheme doesn't seem to be exported from @mui/material/styles anymore, although it is still mentioned in the docs.

Sorry, that's a mistake. It should not appear in the docs, I am fixing it.

joserodolfofreitas pushed a commit to joserodolfofreitas/material-ui that referenced this pull request Jul 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
customization: theme Centered around the theming features package: material-ui Specific to @mui/material v6.x
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[pigment-css] theme does not contain other properties except vars
6 participants