跳至內容
+

遷移至 Pigment CSS

本指南協助您將 Pigment CSS 與 Material UI v6 整合。

在閱讀本指南之前,請確保您已升級至 Material UI v6

簡介

Material UI v6 的預設樣式引擎是 Emotion。它讓您以 CSS-in-JS 的方式編寫樣式,這對於依賴狀態和 props 的動態樣式非常棒。然而,當涉及到頻繁的重新渲染時,它有一些效能上的缺點,因為樣式重新計算發生在客戶端。它也不完全支援 React 伺服器組件,這是一種新的渲染範例,會在伺服器上提前渲染組件。

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.
      }}
    />
  );
}

我們建議閱讀以下指南的其餘部分,以了解新的樣式範例和建立動態樣式的模式。

遷移自訂主題

移除擁有者狀態

由於 Pigment CSS 是一種建置時提取工具,它不支援透過回呼的擁有者狀態。以下範例會在建置時拋出錯誤

const theme = createTheme({
  components: {
    MuiCard: {
      styleOverrides: {
        root: {
          color: ({ ownerState }) => ({
            // ❌ Pigment CSS cannot extract this style.
            ...(ownerState.variant === 'outlined' && {
              borderWidth: 3,
            }),
          }),
        },
      },
    },
  },
});

執行以下 codemod 以從主題中移除擁有者狀態

npx @mui/codemod@latest v6.0.0/theme-v6 next.config.mjs

在某些情況下,codemod 無法移除擁有者狀態。在這種情況下,您必須手動將擁有者狀態替換為 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>
   )
 }