迁移到 Pigment CSS
本指南帮助您将 Pigment CSS 与 Material UI v6 集成。
在阅读本指南之前,请确保您已升级到 Material UI v6。
简介
Material UI v6 的默认样式引擎是 Emotion。它允许您以 CSS-in-JS 的方式编写样式,这非常适合依赖于状态和 props 的动态样式。但是,在频繁重新渲染时,它存在一些性能缺陷,因为样式重新计算发生在客户端。它也不完全支持 React Server Components,这是一种新的渲染范例,可在服务器上提前渲染组件。
Pigment CSS 旨在解决这些问题,同时保持以 CSS-in-JS 方式编写样式的相同开发者体验。它可以与 Emotion 协同工作以简化迁移过程,但建议最终完全迁移到 Pigment CSS。
支持的框架
Pigment CSS 可以与以下框架之一一起使用
- Next.js App Router,搭配 webpack v5 (尚不支持 Turbopack)
- Vite
安装
首先,安装 Pigment CSS 的 Material UI 包装器包
npm install @mui/material-pigment-css @pigment-css/react
npm install --save-dev @pigment-css/nextjs-plugin
然后,打开 Next.js 配置文件并添加插件
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);
最后,在布局文件的顶部导入样式表。
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 lang="en">
<body className={`${inter.className}`}>
{props.children}
</body>
</html>
);
}
Vite
将 Vite 插件作为开发依赖项安装
npm install --save-dev @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.
],
});
最后,将 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>,
);
配置主题
将 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 */
+ }),
};
如果您有自定义主题,请接下来按照主题迁移说明进行操作。否则,您现在可以启动开发服务器了
npm run dev
打开浏览器并导航到 localhost URL,您应该看到应用正在使用 Pigment CSS 运行。
Next.js 字体优化
如果您使用 next/font
来优化字体加载,请将 CSS 变量名传递给字体配置的 variable
属性,并在 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 lang="en">
+ <body className={roboto.variable}>
{children}
</body>
</html>
);
}
最后,使用在上一步中创建的变量更新 typography.fontFamily
值
const pigmentConfig = {
transformLibraries: ['@mui/material'],
theme: createTheme({
+ typography: {
+ fontFamily: 'var(--my-font-family)',
+ },
}),
};
TypeScript
如果您正在使用 TypeScript,则需要使用 Material UI Theme
扩展 Pigment CSS 主题类型。将以下代码添加到包含在您的 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,
}));
您应该在编辑器中看不到 TypeScript 错误。最后,删除测试代码。
工作原理
当通过框架打包器配置 Pigment CSS 插件时,它会拦截 Material UI 使用的样式 API,并将其替换为 Pigment CSS 的 API。然后,Pigment CSS 在构建时提取样式并将它们注入到样式表中。
如果您来自 Material UI v5,那么使用 Pigment CSS 在编写样式方面将是一种范式转变。由于 Pigment CSS 是一种构建时提取工具,因此它不支持依赖于运行时变量的动态样式。例如,对于依赖于如下状态的样式,Pigment CSS 将抛出错误
import Card from '@mui/material/Card';
function App() {
const [color, setColor] = useState('#000000');
return (
<Card
sx={{
color, // ❌ Pigment CSS cannot extract this style.
}}
/>
);
}
我们建议阅读以下指南的其余部分,以了解新的样式范式和创建动态样式的模式。
迁移自定义主题
移除 owner state
由于 Pigment 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 state
npx @mui/codemod@latest v6.0.0/theme-v6 next.config.mjs
在某些情况下,codemod 无法移除 owner state。在这些情况下,您必须手动将 owner state 替换为 variants
。
基于调色板的动态颜色
如果您有基于主题调色板的动态颜色,则可以使用 variants
属性为每个调色板定义样式。
const theme = createTheme({
components: {
MuiCard: {
styleOverrides: {
root: ({ theme, ownerState }) => ({
color: theme.palette[ownerState.palette]?.main,
}),
},
},
},
});
默认 props 提供器
在您的主应用程序文件中使用 DefaultPropsProvider
,并将所有组件默认 props 移动到其中
import { createTheme } from '@mui/material';
const customTheme = createTheme({
// ...other tokens.
components: {
MuiButtonBase: {
- defaultProps: {
- disableRipple: true,
- },
},
MuiSelect: {
- defaultProps: {
- IconComponent: DropdownIcon,
- },
}
}
});
迁移动态样式
sx prop
运行以下 codemod
npx @mui/codemod@latest 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',
}}
/>
))}
</div>
自定义组件
使用 Pigment CSS,任何 JSX 元素都可以接受 sx
prop,因此不再需要将 sx
prop 传递给 Material UI 组件。
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';
然后,运行以下 codemod
npx @mui/codemod@latest v6.0.0/styled path/to/folder
以下场景未被 codemod 覆盖,因此您必须手动更新它们
基于 props 的动态样式
如果您有基于 props 的动态样式,则需要将它们移动到 CSS 变量。您将需要创建一个包装器组件来使用 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;
迁移布局组件
要使用与 Pigment CSS 兼容的布局组件,请将以下组件替换为来自适配器包的组件
-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';
迁移 Box 组件
选择以下方法之一
继续使用 Box
将 Box
组件替换为来自适配器包的组件
-import Box from '@mui/material/Box';
+import Box from '@mui/material-pigment-css/Box';
使用 HTML 元素
Pigment CSS 可以从任何 JSX 元素中提取 sx
prop,因此无需使用 Box 组件。
-import Box from '@mui/material/Box';
function CustomCard() {
return (
- <Box sx={{ display: 'flex' }}>
- <Box component="img" src="..." sx={{ width: 24, height: 24 }}>
- ...
- </Box>
+ <div sx={{ display: 'flex' }}>
+ <img src="..." sx={{ width: 24, height: 24 }}>
+ ...
+ </div>
);
}
对于 TypeScript 用户,您需要扩展 HTMLAttributes
接口以支持 sx
prop。将以下代码添加到包含在您的 tsconfig.json
中的文件中
import type { Theme, SxProps } from '@mui/material/styles';
declare global {
namespace React {
interface HTMLAttributes<T> {
sx?: SxProps<Theme>;
}
interface SVGProps<T> {
sx?: SxProps<Theme>;
}
}
}
迁移 useTheme hook
如果您正在使用 useTheme
hook,请替换导入源
-import { useTheme } from '@mui/material/styles';
+import { useTheme } from '@mui/material-pigment-css';
从右到左支持
使用以下代码更新配置文件以启用从右到左支持
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
,请使用 RtlProvider
包装您的应用程序,并使用 useRtl
hook 来获取方向
+ import RtlProvider from '@mui/material-pigment-css/RtlProvider';
function App() {
+ const [rtl, setRtl] = React.useState(false);
return (
+ <RtlProvider value={rtl}>
{/* Your app */}
+ </RtlProvider>
)
}