跳到内容
+

迁移到 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 可以与以下框架之一一起使用

安装

首先,安装 Pigment CSS 的 Material UI 包装器包

npm install @mui/material-pigment-css @pigment-css/react

然后,根据您的框架按照说明进行操作

Next.js

将 Next.js 插件作为开发依赖项安装

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);

最后,在布局文件的顶部导入样式表。

app/layout.(js|tsx)
 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.mjsvite.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 样式表添加到主文件的顶部。

src/main.(js|tsx)
 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.jsVite 配置文件

+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 中使用它

app/layout.tsx
 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

next.config.mjs
 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>
   )
 }