-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
Changes from 2 commits
7d70305
eaa46fd
0eb8b00
8ad6741
3666d86
167d5fd
5145581
042c4b8
d4b1215
6a633bd
4c8f45f
72bea2d
d55f3e6
f8f21c4
06db99f
83aa481
045a47c
a2f90d5
e3790cc
6524424
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { expect } from 'chai'; | ||
import { stringifyTheme } from './stringifyTheme'; | ||
import extendTheme from './extendTheme'; | ||
|
||
describe('StringifyTheme', () => { | ||
it('should serialize the theme', () => { | ||
const theme = extendTheme(); | ||
const result = stringifyTheme({ | ||
breakpoints: theme.breakpoints, | ||
transitions: theme.transitions, | ||
}); | ||
expect(result).to.equal(`import createBreakpoints from '@mui/system/createBreakpoints'; | ||
import { createTransitions } from '@mui/material/styles'; | ||
|
||
const theme = { | ||
"breakpoints": { | ||
"keys": [ | ||
"xs", | ||
"sm", | ||
"md", | ||
"lg", | ||
"xl" | ||
], | ||
"values": { | ||
"xs": 0, | ||
"sm": 600, | ||
"md": 900, | ||
"lg": 1200, | ||
"xl": 1536 | ||
}, | ||
"unit": "px" | ||
}, | ||
"transitions": { | ||
"easing": { | ||
"easeInOut": "cubic-bezier(0.4, 0, 0.2, 1)", | ||
"easeOut": "cubic-bezier(0.0, 0, 0.2, 1)", | ||
"easeIn": "cubic-bezier(0.4, 0, 1, 1)", | ||
"sharp": "cubic-bezier(0.4, 0, 0.6, 1)" | ||
}, | ||
"duration": { | ||
"shortest": 150, | ||
"shorter": 200, | ||
"short": 250, | ||
"standard": 300, | ||
"complex": 375, | ||
"enteringScreen": 225, | ||
"leavingScreen": 195 | ||
} | ||
} | ||
}; | ||
|
||
theme.breakpoints = createBreakpoints(theme.breakpoints || {}); | ||
theme.transitions = createTransitions(theme.transitions || {}); | ||
|
||
export default theme;`); | ||
|
||
// test that non-seriazable values still exist in the original theme | ||
expect(typeof theme.generateStyleSheets).to.equal('function'); | ||
}); | ||
|
||
it('should serialize the custom theme', () => { | ||
const theme = extendTheme({ | ||
breakpoints: { | ||
values: { | ||
mobile: 0, | ||
tablet: 640, | ||
laptop: 1024, | ||
desktop: 1280, | ||
} as any, | ||
}, | ||
transitions: { | ||
duration: { | ||
standard: 432, | ||
}, | ||
}, | ||
}); | ||
const result = stringifyTheme({ | ||
breakpoints: theme.breakpoints, | ||
transitions: theme.transitions, | ||
}); | ||
expect(result).to.equal(`import createBreakpoints from '@mui/system/createBreakpoints'; | ||
import { createTransitions } from '@mui/material/styles'; | ||
|
||
const theme = { | ||
"breakpoints": { | ||
"keys": [ | ||
"mobile", | ||
"tablet", | ||
"laptop", | ||
"desktop" | ||
], | ||
"values": { | ||
"mobile": 0, | ||
"tablet": 640, | ||
"laptop": 1024, | ||
"desktop": 1280 | ||
}, | ||
"unit": "px" | ||
}, | ||
"transitions": { | ||
"duration": { | ||
"standard": 432, | ||
"shortest": 150, | ||
"shorter": 200, | ||
"short": 250, | ||
"complex": 375, | ||
"enteringScreen": 225, | ||
"leavingScreen": 195 | ||
}, | ||
"easing": { | ||
"easeInOut": "cubic-bezier(0.4, 0, 0.2, 1)", | ||
"easeOut": "cubic-bezier(0.0, 0, 0.2, 1)", | ||
"easeIn": "cubic-bezier(0.4, 0, 1, 1)", | ||
"sharp": "cubic-bezier(0.4, 0, 0.6, 1)" | ||
} | ||
} | ||
}; | ||
|
||
theme.breakpoints = createBreakpoints(theme.breakpoints || {}); | ||
theme.transitions = createTransitions(theme.transitions || {}); | ||
|
||
export default theme;`); | ||
|
||
// test that non-seriazable values still exist in the original theme | ||
expect(typeof theme.generateStyleSheets).to.equal('function'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
import { isPlainObject } from '@mui/utils/deepmerge'; | ||
|
||
function isSerializable(val: any) { | ||
return ( | ||
isPlainObject(val) || | ||
typeof val === 'undefined' || | ||
typeof val === 'string' || | ||
typeof val === 'boolean' || | ||
typeof val === 'number' || | ||
Array.isArray(val) | ||
); | ||
} | ||
|
||
/** | ||
* `baseTheme` usually comes from `createTheme` or `extendTheme`. | ||
* | ||
* This function is intended to be used with zero-runtime CSS-in-JS like Pigment CSS | ||
* For example, in a Next.js project: | ||
* | ||
* ```js | ||
* // next.config.js | ||
* const { extendTheme } = require('@mui/material/styles'); | ||
* | ||
* const theme = extendTheme(); | ||
* // `.toRuntime` is Pigment CSS specific to create a theme that is available at runtime. | ||
siriwatknp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* theme.toRuntime = () => stringifyTheme(theme); | ||
siriwatknp marked this conversation as resolved.
Show resolved
Hide resolved
siriwatknp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* module.exports = withPigment({ | ||
* theme, | ||
* }); | ||
* ``` | ||
*/ | ||
export function stringifyTheme(baseTheme: Record<string, any> = {}) { | ||
const serializableTheme: Record<string, any> = { ...baseTheme }; | ||
|
||
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]); | ||
} | ||
} | ||
} | ||
Comment on lines
+37
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Then for these dynamic values (have CSS variables), solve it with: https://github.com/mui/pigment-css/issues/129. But honestly, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But how would you make it work for components that are using At this point, I think we need it (can be an internal API) otherwise v6 will be delayed for at least a month.
No, we are doing this because some components are accessing the theme at runtime (those that call
Again, I think I mentioned in some issue to you that
To me, both options give almost the same result, (1) is a bit better because there is no What's your opinion? cc @mnajdova @brijeshb42 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://nextjs.org/docs/app/building-your-application/caching#react-cache-function doesn't make it so clear.
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 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @DiegoAndai |
||
|
||
serializeTheme(serializableTheme); | ||
|
||
return `import createBreakpoints from '@mui/system/createBreakpoints'; | ||
import { createTransitions } from '@mui/material/styles'; | ||
|
||
const theme = ${JSON.stringify(serializableTheme, null, 2)}; | ||
|
||
theme.breakpoints = createBreakpoints(theme.breakpoints || {}); | ||
theme.transitions = createTransitions(theme.transitions || {}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also found that we'll have to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it works. The theme.shadows are in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
export default theme;`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.