拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Material UI 迁移到 Pigment CSS 的完整实战指南:构建时样式提取与动态样式改写

Material UI 迁移到 Pigment CSS 的完整实战指南构建时样式提取与动态样式改写【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui本文以 Material UI 官方迁移文档migrating-to-pigment-css为主体系统讲解如何用 Pigment CSS 替换默认的 Emotion 样式引擎从框架支持范围、Next.js / Vite 插件接入、主题配置到 owner state、动态sx、styled、布局组件、RTL 等逐类迁移手法并结合仓库中mui/material-pigment-css包装包源码与官方示例项目帮助你在保持 CSS-in-JS 开发体验的前提下完成向构建时样式提取方案的完整迁移。背景为什么从 Emotion 迁移到 Pigment CSSMaterial UI 的默认样式引擎是 Emotion它允许以 CSS-in-JS 的方式编写样式非常适合依赖状态和 props 的动态样式但在频繁重渲染的场景下有明显的性能短板——样式重算发生在客户端。同时Emotion 也不完整支持 React Server ComponentsRSC这种在服务器端预渲染组件的新渲染范式。Pigment CSS 的目标是在保留「用 CSS-in-JS 语法写样式」这一开发者体验的同时解决上述问题。它可以在迁移期间与 Emotion 并存但官方最终建议完全迁移到 Pigment CSS。需要注意的前提是迁移前置条件先完成 Material UI v7或 v6的升级再进行本次迁移成熟度提示Pigment CSS 目前处于早期 alpha 阶段官方在持续改进其性能与稳定性遇到问题应向上游反馈 issue。支持的框架Pigment CSS 目前支持两类框架接入方式Next.js App Routerwebpack v5暂不支持 TurbopackVite。安装第一步安装 Material UI 的 Pigment CSS 包装包及 React 运行时包npm install mui/material-pigment-css pigment-css/reactpnpm add mui/material-pigment-css pigment-css/reactyarn add mui/material-pigment-css pigment-css/react从 包装包清单 可以确认几个实现事实该包名为mui/material-pigment-css定位是「提供与 Material UI 相同 styled 与主题 API 的 Pigment CSS 封装」peerDependencies声明了对pigment-css/react版本^0.0.30 || ^0.0.31的依赖且sideEffects为false便于打包器做 tree-shaking。Next.js 接入先以开发依赖安装 Next.js 插件npm install --save-dev pigment-css/nextjs-pluginpnpm add -D pigment-css/nextjs-pluginyarn add -D pigment-css/nextjs-plugin然后在 Next.js 配置文件中引入插件next.config.mjsESM 写法import { withPigment } from pigment-css/nextjs-plugin; const nextConfig { // ...Your nextjs config. }; /** * type {import(pigment-css/nextjs-plugin).PigmentOptions} */ const pigmentConfig { transformLibraries: [mui/material], }; export default withPigment(nextConfig, pigmentConfig);CommonJS 写法next.config.jsconst { withPigment } require(pigment-css/nextjs-plugin); const nextConfig { // ...Your nextjs config. }; /** * type {import(pigment-css/nextjs-plugin).PigmentOptions} */ const pigmentConfig { transformLibraries: [mui/material], }; module.exports withPigment(nextConfig, pigmentConfig);最后在 layout 文件顶部导入样式表import type { Metadata } from next; import { Inter } from next/font/google; import mui/material-pigment-css/styles.css; export default function RootLayout(props) { return ( html langen body className{${inter.className}} {props.children} /body /html ); }仓库中的官方示例 examples/material-ui-pigment-css-nextjs-ts/next.config.mjs 展示了完整形态的配置其中主题额外启用了cssVariables.colorSchemeSelector: class与colorSchemes: { light: true, dark: true }来支持明暗双主题切换可作为生产参考import { withPigment } from pigment-css/nextjs-plugin; import { createTheme } from mui/material; const nextConfig {}; export default withPigment(nextConfig, { theme: createTheme({ cssVariables: { colorSchemeSelector: class, }, colorSchemes: { light: true, dark: true }, typography: { fontFamily: var(--font-roboto), }, }), transformLibraries: [mui/material], });Vite 接入以开发依赖安装 Vite 插件npm install --save-dev pigment-css/vite-pluginyarn add -D pigment-css/vite-plugin在 Vite 配置文件通常是vite.config.mjs或vite.config.js中加入插件import { defineConfig } from vite; import { pigment } from pigment-css/vite-plugin; /** * type {import(pigment-css/vite-plugin).PigmentOptions} */ const pigmentConfig { transformLibraries: [mui/material], }; export default defineConfig({ plugins: [ pigment(pigmentConfig), // ... Your other plugins. ], });已知问题pnpm存在一个 pnpm 的已知问题目前会阻止插件在该包管理器下正常工作。在问题解决前必须改用 npm 或 yarn。最后在主文件顶部导入 Pigment CSS 样式表import * as React from react; import mui/material-pigment-css/styles.css; import App from ./App; ReactDOM.createRoot(document.getElementById(root)).render( React.StrictMode App / /React.StrictMode, );这与官方示例 examples/material-ui-pigment-css-vite-ts/src/main.tsx 的实际写法一致示例中还额外引入了pigment-css/react的 React 运行时与App.tsx其 vite.config.ts 则演示了带cssVariables: true与colorSchemes双主题的完整pigmentConfig。配置主题将 Pigment CSS 与 Material UI 集成时必须把主题传入插件配置。在 Next.js 或 Vite 的配置文件中加入import { createTheme } from mui/material; const pigmentConfig { transformLibraries: [mui/material], theme: createTheme({ cssVariables: true, /* other parameters, if any */ }), };cssVariables: true是让主题令牌以 CSS 变量形式产出的关键开关——后文「动态值迁移到 CSS 变量」的模式如var(--mui-palette-primary-solidBg)正是依赖它。如果你有自定义主题请继续阅读下文「迁移自定义主题」否则直接启动开发服务器验证npm run devpnpm devyarn dev打开浏览器访问 localhost即可看到以 Pigment CSS 渲染的应用。Next.js 字体优化如果你在用next/font优化字体加载需要给字体配置的variable属性传入一个 CSS 变量名并在 body 的 className 中使用它import { Roboto } from next/font/google; const roboto Roboto({ weight: [300, 400, 500, 700], subsets: [latin], display: swap, variable: --my-font-family, }); export default function RootLayout(props) { const { children } props; return ( html langen body className{roboto.variable} {children} /body /html ); }然后把主题里的typography.fontFamily更新为该变量const pigmentConfig { transformLibraries: [mui/material], theme: createTheme({ typography: { fontFamily: var(--my-font-family), }, }), };之所以要走 CSS 变量而不是直接写字体族名字符串是因为 Pigment CSS 是构建时提取工具fontFamily这类会被内联进组件样式的属性需要以静态可提取的形式存在。TypeScript 类型扩展使用 TypeScript 时需要把 Pigment CSS 的主题类型扩展为 Material UI 的Theme。在任意被tsconfig.json收录的文件中添加// e.g. App.tsx import { Theme } from mui/material/styles; declare module mui/material-pigment-css { interface ThemeArgs { theme: Theme; } }然后用下面的代码验证类型是否被 Pigment CSS 正确拾取// e.g. App.tsx import { styled } from mui/material-pigment-css; const TestThemeTypes styled(div)(({ theme }) ({ color: theme.palette.primary.main, }));编辑器中应无类型报错。验证完成后删除测试代码。仓库示例 examples/material-ui-pigment-css-vite-ts/src/material-ui-pigment-css.d.ts 展示了这一声明文件的完整形态除了上面的ThemeArgs模块扩展外还同时声明了全局HTMLAttributes/SVGProps上的sx属性见下文「Box 组件迁移」。工作原理构建时提取的范式转换当 Pigment CSS 插件通过框架打包器配置后它会拦截 Material UI 使用的样式 API 并将其替换为 Pigment CSS 对应的实现然后在构建时提取样式并注入到样式表中。从 packages/mui-material-pigment-css/package.json 的wyw-in-js字段可以印证这一机制styled、sx、keyframes、css、useTheme等标签分别映射到pigment-css/react/exports/...下的对应实现供支持 write-you-want 的构建工具在编译期做静态分析。而包装包入口 src/index.ts 直接export * from pigment-css/react及其theme子模块即该包本质是 Material UI 语义下的 Pigment CSS 再导出层。如果来自 Material UI v5使用 Pigment CSS 意味着样式编写范式的一次转换由于它是构建时提取工具不支持依赖运行时变量的动态样式。例如以下依赖 state 的样式会在构建时抛出错误import Card from mui/material/Card; function App() { const [color, setColor] useState(#000000); return ( Card sx{{ color, // ❌ Pigment CSS cannot extract this style. }} / ); }正确做法是遵循本文后续各节的模式静态部分留在sx/styled动态部分通过内联style属性设置 CSS 变量。迁移自定义主题移除 owner statePigment CSS 同样不支持通过回调读取 owner state。下面这个主题写法会在构建时报错const theme createTheme({ components: { MuiCard: { styleOverrides: { root: { color: ({ ownerState }) ({ // ❌ Pigment CSS cannot extract this style. ...(ownerState.variant outlined { borderWidth: 3, }), }), }, }, }, }, });先运行官方 codemod 自动移除主题中的 owner statenpx mui/codemodlatest v6.0.0/theme-v6 next.config.mjs对于 codemod 无法处理的情况需要手动把 owner state 逻辑改写为variants。基于 palette 的动态颜色如果主题里存在依赖主题 palette 的动态颜色可以用variants为每个 palette 分别声明样式const theme createTheme({ components: { MuiCard: { styleOverrides: { root: ({ theme, ownerState }) ({ color: theme.palette[ownerState.palette]?.main, }), }, }, }, });const theme createTheme({ components: { MuiCard: { styleOverrides: { root: ({ theme }) ({ variants: [ ...Object.entries(theme.palette) .filter(([, palette]) palette palette.main) .map(([palette, { main }]) ({ props: { palette }, style: { color: main, }, })), ], }), }, }, }, });DefaultPropsProvider把主题components下各组件的defaultProps从构建配置中移除改用DefaultPropsProvider在应用主文件中提供import { createTheme } from mui/material; const customTheme createTheme({ // ...other tokens. components: { MuiButtonBase: { - defaultProps: { - disableRipple: true, - }, }, MuiSelect: { - defaultProps: { - IconComponent: DropdownIcon, - }, } } }); import DefaultPropsProvider from mui/material/DefaultPropsProvider; function App() { return ( DefaultPropsProvider value{{ MuiButtonBase: { disableRipple: true, }, MuiSelect: { IconComponent: DropdownIcon, }, }} {/* Your app */} /DefaultPropsProvider ); }这样做的意义在于主题配置需要被构建时插件静态读取主题里不能再夹带运行时行为而默认 props 属于运行时关注点天然适合交给 React 组件树中的 Provider 承载。迁移动态样式sx prop先运行 codemod 处理sxpropnpx mui/codemodlatest v6.0.0/sx-prop path/to/folder以下场景不在 codemod 覆盖范围内需要手动处理。动态值值依赖变量时需要把它移到内联样式中的 CSS 变量。以列表项尺寸递减 条件背景色为例div {items.map((item, index) ( Box key{index} sx{{ borderRadius: 50%, width: max(${6 - index}px, 3px), height: max(${6 - index}px, 3px), bgcolor: index 0 ? primary.solidBg : background.level3, }} / ))} /divdiv {items.map((item, index) ( Box key{index} sx{{ borderRadius: 50%, width: max(6px - var(--offset), 3px), height: max(6px - var(--offset), 3px), bgcolor: var(--bg), }} style{{ --offset: ${index}px, --bg: index 0 ? var(--mui-palette-primary-solidBg) : var(--mui-palette-background-level3), }} / ))} /div改写要点sx中只保留可静态提取的片段如把${6 - index}px拆成6px - var(--offset)运行时变量统一由style内联属性以 CSS 变量形式注入引用主题令牌时直接使用cssVariables: true产出的--mui-palette-*系列变量。自定义组件透传 sx在 Pigment CSS 下任意 JSX 元素都能接受sxprop因此自定义组件不再需要把sx一路向下透传给 Material UI 组件。例如下面组件可以删掉sx合并逻辑直接把样式写在元素上import ButtonBase from mui/material/ButtonBase; function ActiveButton({ sx, ...props }) { return ( ButtonBase sx{[ { :active: { opacity: 0.5, }, }, - ...Array.isArray(sx) ? sx : [sx], ]} {...props} / ); }styled自定义组件若使用了来自mui/material/styles的styled把导入来源改为mui/material-pigment-css-import { styled } from mui/material/styles; import { styled } from mui/material-pigment-css;再运行 codemodnpx mui/codemodlatest v6.0.0/styled path/to/folder同样存在 codemod 不覆盖的场景需手动处理。基于 props 的动态样式styled中依赖 props 的动态样式需要改写为 CSS 变量并通过一个包装组件以内联样式设置这些变量const FlashCode styled(div)( ({ theme, startLine 0, endLine startLine, lineHeight 0.75rem }) ({ top: calc(${lineHeight} * 1.5 * ${startLine}), height: calc(${lineHeight} * 1.5 * ${endLine - startLine 1}), ...theme.typography.caption, }), ); export default FlashCode;const FlashCodeRoot styled(div)(({ theme }) ({ top: calc(var(--Flashcode-lineHeight) * 1.5 * var(--Flashcode-startLine)), height: calc(var(--Flashcode-lineHeight) * 1.5 * (var(--Flashcode-endLine) - var(--Flashcode-startLine) 1)), ...theme.typography.caption, })); const FlashCode React.forwardRef(function FlashCode(props, ref) { const { children, startLine 0, endLine startLine, lineHeight 0.75rem, ...other } props; return ( FlashCodeRoot ref{ref} {...other} style{{ --Flashcode-lineHeight: lineHeight, --Flashcode-startLine: startLine, --Flashcode-endLine: endLine, ...other.style, }} {children} /FlashCodeRoot ); }); export default FlashCode;注意styled部分只保留对theme的依赖与纯静态结构所有运行时输入startLine、endLine、lineHeight都经包装组件以 CSS 变量注入同时保留forwardRef与...other.style合并以维持对外行为兼容。迁移布局组件要使用与 Pigment CSS 兼容的布局组件把下列组件的导入来源替换为 adapter 包-import Container from mui/material/Container; import Container from mui/material-pigment-css/Container; -import Grid from mui/material/Grid; import Grid from mui/material-pigment-css/Grid; -import Stack from mui/material/Stack; import Stack from mui/material-pigment-css/Stack; -import Hidden from mui/material/Hidden; import Hidden from mui/material-pigment-css/Hidden;行为差异mui/material-pigment-css/Grid的Grid与默认Grid行为不同——它用 CSSgap而非margin来产生项目间距。迁移时请留意布局视觉差异。从源码看这些适配器都是极薄的转发层如 src/Grid/Grid.ts 仅import Grid from pigment-css/react/Grid后默认导出src/Box/Box.ts 同理转发pigment-css/react/Boxsrc/RtlProvider/RtlProvider.ts 则从mui/system/RtlProvider导出RtlProvider与useRtl。结合 package.json 的 exports 字段./*: ./src/*/index.ts可知mui/material-pigment-css/Container这类子路径导入是如何落到具体实现的。迁移 Box 组件有两种选择方案一继续使用 Box把Box换成 adapter 包中的版本-import Box from mui/material/Box; import Box from mui/material-pigment-css/Box;方案二直接使用 HTML 元素Pigment CSS 可以从任意 JSX 元素提取sxprop因此很多场景不再需要Box-import Box from mui/material/Box; function CustomCard() { return ( - Box sx{{ display: flex }} - Box componentimg src... sx{{ width: 24, height: 24 }} - ... - /Box div sx{{ display: flex }} img src... sx{{ width: 24, height: 24 }} ... /div ); }对于TypeScript用户需要扩展HTMLAttributes接口以支持sxprop。在任意被tsconfig.json收录的文件中添加import type { Theme, SxProps } from mui/material/styles; declare global { namespace React { interface HTMLAttributesT { sx?: SxPropsTheme; } interface SVGPropsT { sx?: SxPropsTheme; } } }这与官方示例 material-ui-pigment-css.d.ts 中的全局声明一致示例同时完成了ThemeArgs模块扩展与HTMLAttributes/SVGProps的sx扩展且额外导入了mui/material/themeCssVarsAugmentation以补全 CSS 变量令牌类型。迁移 useTheme Hook如果使用了useTheme替换导入来源-import { useTheme } from mui/material/styles; import { useTheme } from mui/material-pigment-css;Next.js App Router 注意useThemehook 只在 React Client Components 中可用使用它的组件需要添加use client指令。右键至左RTL支持在配置文件中加入以下代码启用 RTL 支持const pigmentConfig { theme: createTheme(), css: { // Specify your default CSS authoring direction defaultDirection: ltr, // Generate CSS for the opposite of the defaultDirection // This is set to false by default generateForBothDir: true, }, }从 theme.direction 迁移如果组件里在使用theme.direction判断方向改为用RtlProvider包裹应用并通过useRtlhook 获取方向 import RtlProvider from mui/material-pigment-css/RtlProvider; function App() { const [rtl, setRtl] React.useState(false); return ( RtlProvider value{rtl} {/* Your app */} /RtlProvider ) }- import { useTheme } from mui/material/styles; import { useRtl } from mui/material-pigment-css/RtlProvider; function App() { - const theme useTheme(); const isRtl useRtl(); return ( Box sx{{ display: flex, alignItems: center, pl: 1, pb: 1 }} IconButton aria-labelprevious - {theme.direction rtl ? SkipNextIcon / : SkipPreviousIcon /} {isRtl ? SkipNextIcon / : SkipPreviousIcon /} /IconButton IconButton aria-labelplay/pause PlayArrowIcon sx{{ height: 38, width: 38 }} / /IconButton IconButton aria-labelnext - {theme.direction rtl ? SkipPreviousIcon / : SkipNextIcon /} {isRtl ? SkipPreviousIcon / : SkipNextIcon /} /IconButton /Box ); }由于方向信息在构建时无法静态判定RtlProvider/useRtl把「运行时方向」从主题读取通道中剥离出来交由 React 上下文承载而 CSS 侧的双向规则则由css.generateForBothDir在构建时生成两者配合覆盖了静态样式与运行时判断两个层面。迁移要点速查迁移项操作自动化工具主题中的 owner state移除回调改写为variantsnpx mui/codemodlatest v6.0.0/theme-v6 next.config.mjs主题中的 defaultProps移入DefaultPropsProvider手动sx中的动态值静态部分留sx动态部分用style注入 CSS 变量npx mui/codemodlatest v6.0.0/sx-prop path/to/folderstyled导入与依赖 props 的动态样式改从mui/material-pigment-css导入动态值改 CSS 变量npx mui/codemodlatest v6.0.0/styled path/to/folderBox/Container/Grid/Stack/Hidden改从mui/material-pigment-css子路径导入或直接用 HTML 元素 sx手动useTheme改从mui/material-pigment-css导入App Router 下组件需use client手动RTL配置css.defaultDirection/generateForBothDir组件用RtlProvideruseRtl手动TypeScript扩展ThemeArgs与全局sx属性手动最后需要强调适用前提本文所有配置与命令基于当前仓库中mui/material-pigment-cssv9.4.0peerDependencies要求pigment-css/react^0.0.30 || ^0.0.31与配套示例项目Pigment CSS 生态仍处于 alpha 阶段升级pigment-css/*相关包前建议关注其官方 issue 跟踪特别是 Vite pnpm 组合的已知限制。【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门