iT邦幫忙

2023 iThome 鐵人賽

DAY 5
0

現在有了 MUI ,還有跟其相搭配的 emotion,樣式的調整上已經很方便了,但是目前 NextJS 已經開始預設使用 React Server Component ,而 emotion 還無法支援這樣的元件形式,會導致最後所有元件還是回歸 client 這邊 ,無法享受到 Server Component 的好處。

所以這邊試著加入 TailwindCSS 來改善這點,在Server Component 上可以使用 tailwind 來改樣式,也能用來局部修改 MUI 的樣式,減少因 CSS-in-JS 產出的 css 檔案所佔的空間。

老實說以整體樣式管理上來說這不是個好辦法,因為 MUI 也有自己一套主題樣式的架構,所以之後還要想辦法縫合兩邊的樣式主題管理架構,免得一國兩制。

首先幫 app 這邊設定好 tailwind,來安裝套件,這邊跟一般的 tailwind 安裝相同。

pnpm add -D tailwindcss@latest postcss@latest autoprefixer@latest

到目標專案初始化 tailwind 設定。

cd apps/ironman-nextjs
npx tailwindcss init -p

改 postcss 設定,因為跟平常不同 config 檔不在根目錄上。

// apps/ironman-nextjs/postcss.config.js
const { join } = require('path');

module.exports = {
  plugins: {
	    tailwindcss: { config: join(__dirname, 'tailwind.config.js') }, // 指定目前 app 的 config
    autoprefixer: {},
  },
};

再來是指定 tailwind 要掃描的範圍,tailwind 會根據指定的掃描範圍內找到的 class 來產生對應的 css 檔案,並捨去掉沒有用到的 class ,來輕量化樣式檔案。

因為在 monorepo 架構下很多元件是在 app 之外的,要讓 tailwind 也查到那些元件的家門上不然就不會生成樣式,這裡 NX 提供了輔助, createGlobPatternsForDependencies 會偵測目前專案有依賴的 lib ,產生對應的目錄資料給 tailwind 使用。

// apps/ironman-nextjs/tailwind.config.js
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const { join } = require('path');

module.exports = {
  content: [
    join(
      __dirname,
      '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}'
    ),
    ...createGlobPatternsForDependencies(__dirname),
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

在根節點上引入 tailwind 樣式

/* apps/ironman-nextjs/app/global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// apps/ironman-nextjs/app/layout.tsx
import './global.css';

另外在用 NX 指令建置的時候要特別指定 postcss 的 config 位置

// apps/ironman-nextjs/project.json
{
  "targets": {
    "build": {
      "executor": "@nx/next:build", 
      "options": {
        // ...
        "postcssConfig": "apps/ironman-nextjs/postcss.config.js"
      },
      // ...
    },
	},
	//...
}

再來為了將 tailwind 運用在 MUI 上要做的設定,tailwind 有自己一套 css reset 的樣式,但我們已經有 MUI 的 CssBaseline 了,所以關掉 tailwind 的。

// apps/ironman-nextjs/tailwind.config.js
// ...

module.exports = {
	// ...
	corePlugins: {
    preflight: false, // 關掉 Tailwind 預設的 css reset,避免跟 MUI 的重複
  },
};

再來因為 MUI 也有自己的預設 class ,如果 tailwind 的 class 權重跟 MUI 的相當的話,就可能出現無法覆蓋樣式的情況。

所以要設定 tailwind 的 important 參數來加強 tailwind 所產生樣式的權重,指定範圍是整個 app 所以用根結點的 id 作為增加權重的標的。

// apps/ironman-nextjs/tailwind.config.js
// ...

module.exports = {
	// ...
  important: '#__next',  // 設定為根節點 id
};
// apps/ironman-nextjs/app/layout.tsx
import './global.css';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@ironman-nextjs/ui/react-components';

//...

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body id="__next"> {/* 加上 id */}
        <CssBaseline /> 
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

另外要變更的是樣式的載入順序,emotion 產生的動態樣式一般會最後載入,導致可能覆蓋掉 tailwind 的樣式,所以要設定成反過來優先載入。

// apps/ironman-nextjs/app/layout.tsx
// ...
import { StyledEngineProvider } from '@mui/material/styles';

// ...
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body id="__next">
        <StyledEngineProvider injectFirst>{/* 優先載入 */}
          <CssBaseline />
          <ThemeProvider>{children}</ThemeProvider>
        </StyledEngineProvider>
      </body>
    </html>
  );
}

設定好之後就能在元件上添加 tailwind class 了,即使遠在 lib 當中的元件也能套上樣式。

import Button from '@mui/material/Button';

<Button variant="contained" className="bg-green-500 hover:bg-green-700">
  Contained
</Button>

最後別忘了還有一個 storybook 要設定,基本上的步驟是相同的,只是在指定 tailwind 的 important 範圍的時候要注意指向 storybook 的元件 iframe 中的根節點,而不是整個 storybook app 的根節點

// libs/ui/react-storybook/tailwind.config.js 

module.exports = {
	// ...
  important: '#storybook-root', // 元件預覽區 iframe 的根節點
  corePlugins: {
    preflight: false,
  },
};

還有因為 storybook 會去抓取自身範圍外的 story 來顯示,所以 tailwind 也需要設定觀察同樣的範圍來生成樣式

// libs/ui/react-storybook/tailwind.config.js
const { join } = require('path');

module.exports = {
  content: [
    join(
      __dirname,
      '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}',
    ),
		// 觀察範圍與 storybook 相同
    join(
      __dirname,
      '../../**/ui/**/src/lib/**/*!(*.stories|*.spec).{ts,tsx,html}',
    ),
  ],
  theme: {
    extend: {},
  },
  important: '#storybook-root',
  plugins: [],
  corePlugins: {
    preflight: false,
  },
};

上一篇
添加 Storybook
下一篇
整合 MUI 跟 Tailwind 樣式設定
系列文
組裝前端開發工具箱,從 NX 入手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言