iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Modern Web

從零開始學習 Next.js系列 第 26

Day26 - 移除沒用到的 CSS,使用 Purge CSS (feat. Ant Design, Tailwind)

前言

在前端的世界中,我們經常會站在巨人的肩膀上,如果任何事情都需要自己從零開始動手做,後續的維護也會耗費可觀的成本。例如想要一個好用的 UI framework,有 Material UIAnt DesignChakraBootstrap 等以上幾種常見的選擇;想要方便的 utility CSS framework,最熱門的選項不外乎是 Tailwind

但是在設定這些 framework 時通常都需要引入整包的套件,像是我們只想要使用部分 Tailwind 的功能,但是明明很多功能都用不到,卻在客戶端載入異常龐大的 CSS 檔案,對於使用者的體驗也會不好。

在這篇文章中我們想要嘗試在 Next.js 中導入 Ant Design 與 Tailwind 兩個套件,然後在 Next.js 使用 postCSS 設定 purge CSS,達到在打包時移除沒用到的 CSS,避免客戶端必須載入無用的 CSS 。

安裝 ant design

在使用 Ant Design 時,我們可能會想修改它預設的 theme,而要修改 Ant Design 的 theme 要透過修改 less 的變數才能達成,但是在 Next.js 中目前還沒有內建支援 less 的 CSS 檔案格式,不過支援 less 的 PR#23185 已經在路上了,我們可以耐心等待有一天這支 PR 被 merge ?。

在還沒支援 less 以前,我們需要手動修改一些 next.config.js 的設定,首先安裝以下幾個套件:

yarn add antd next-compose-plugins next-with-less less less-loader

然後,我們修改 next.config.js 的檔案內容,把 Ant Design 的 primary-colorborder-radius-base 修改成客製化的樣式:

// next.config.js
const withPlugins = require("next-compose-plugins");
const withLess = require("next-with-less");

const plugins = [
  [
    withLess,
    {
      lessLoaderOptions: {
        lessOptions: {
          modifyVars: {
            "primary-color": "#00ccb4",
            "border-radius-base": "4px",
          },
        },
      },
    },
  ],
];

module.exports = withPlugins(plugins, {
  reactStrictMode: true,
});

然後,為了在專案中能夠使用 Ant Design 的樣式,我們需要在 pages/_app.tsx 中引入 antd.less 這個檔案,這樣才能讓專案全域都可以拿到 Ant Design 的樣式:

// _app.tsx
import type { AppProps } from "next/app";
import "antd/dist/antd.less";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
export default MyApp;

最後,測試看看 Ant design 的預設元件 theme 是不是已經被修改,在一個頁面中使用「按鈕元件」,因為 primary-colorborder-radius-base 都被新的設定覆蓋,所以你在畫面上就可以看到修改預設 theme 後的元件。

import { NextPage } from "next";
import { Button } from "antd";

const Home: NextPage = () => {
  return <Button type="primary">my button</Button>;
};

export default Home;

ant design - button

安裝 Tailwind

參考 https://tailwindcss.com/docs/guides/nextjs

接下來我們要在 Next.js 中設定 Tailwind,在 Next.js v10 以上的版本要使用以下指令安裝 Tailwind:

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

接著,再使用以下指令初始化 Tailwind 的環境,以下指令會在專案中產生 tailwind.config.jspostcss.config.js 兩個檔案:

npx tailwindcss init -p

從 Tailwind 官方網站中看到以上指令會在 postcss.config.js 中新增 tailwindcssautoprefixer 兩個 plugins ,而且兩個值都為空物件 {} ,但是我們在這篇文章中不需要做額外的客製化設定,在這裡可以直接在 plugins 的陣列中直接加入兩個參數:

// postcss.config.js
module.exports = {
  plugins: ["tailwindcss", "autoprefixer"],
};

然後我們在修改 pages/index.js 中的內容來測試 Tailwind 是否設定成功,簡單地在 <Button /> 這個元件上設定 m-8 ,代表 margin: 2rem 的意思:

import { Button } from "antd";

const Home = () => {
  return (
    <Button className="sm:m-16 m-8" type="primary">
      my button
    </Button>
  );
};

export default Home;

最後你應該可以在畫面上看到 <Button />margin 成功地被設置,原本緊貼在螢幕邊緣的按鈕,現在多了一些間距:

加上 tailwind

檢查目前的 bundle size

想要測試真實環境下每個頁面會載入的 CSS 檔案大小,必須事先 yarn buildyarn start ,如此一來才能準確地看到 CSS 檔案被額外地載入,如果只是開啟 dev server,CSS 只會被插入在 JavaScript 裡面,無法準確地知道 CSS 檔案的大小。

yarn start 之後,從 Chrome → Network → CSS 中可以看到有一包 CSS 檔案被載入,從畫面中可以看到其大小為 380 kB,是一個不小的數字,明明只有用到一個 Ant Design 的按鈕元件, 也只有用到 Tailwind 的 m-8 這個 CSS selector 而已,理應是一個不大的檔案大小才對。

而且如果每次使用者進入到一個頁面都需要載入這麼大的檔案,對於網路傳輸較慢的使用者將會是一場折磨。

很大的 CSS 檔案

設定 purge CSS

所以為了解決這個問題,我們需要使用 purge CSS 將使用不到的 CSS 從最終的 CSS bundle 中移除。根據官方的說明,使用以下指令安裝所需要的套件:

yarn add @fullhuman/postcss-purgecss postcss-flexbugs-fixes postcss-preset-env

然後在 postcss.config.js 中加上一些額外的設定,讓 purge CSS 可以為我們工作:

module.exports = {
  plugins: [
    "tailwindcss",
    "autoprefixer",
    "postcss-flexbugs-fixes",
    [
      "postcss-preset-env",
      {
        autoprefixer: {
          flexbox: "no-2009",
        },
        stage: 3,
        features: {
          "custom-properties": false,
        },
      },
    ],
    [
      "@fullhuman/postcss-purgecss",
      {
        content: [
          "./pages/**/*.{js,jsx,ts,tsx}",
          "./components/**/*.{js,jsx,ts,tsx}",
        ],
        defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
        safelist: ["html", "body"],
      },
    ],
  ],
};

可是如同上面一樣操作,在 yarn buildyarn start 後看到在頁面中載入的檔案大小已經縮小為 2.7 kB,但是畫面中的按鈕樣式全都不見了:

樣式全部都不見了

會發生這個問題的主要原因是 Ant Design 的 CSS selector 較難以判定,如果使用上面看到的 defaultExtractor 便無法順利地萃取出正確的 selector,因此就會不小心刪掉原本不該刪掉的 selector,最後導致看到的按鈕樣式與預期中的不一樣。

為了解決在 Ant Design 中能夠正確地使用 purge CSS,在 issue#172 中有一個較為正確的解決方案,可以額外在 content 中使用 glob 尋找在 node_modules/antd/es 這個資料夾裡面所有的 CSS 檔案,讓 purge CSS 在執行時不會把 Ant Design 的 CSS 都刪掉:

const glob = require("glob");

...glob.sync("node_modules/antd/es/**/*.css", { noDir: true })

此外,還需要加上一個 extractor 能夠正常萃取出 Ant Design 的 CSS selector:

extractors: [
  {
    extractor: (content) => content.match(/([a-zA-Z-]+)(?= {)/g) || [],
    extensions: ["css"],
  },
],

再設定完後,同樣地,再次 yarn buildyarn start 讓 Next.js 將 CSS 打包成獨立的檔案,方便我們觀察最後的結果。在瀏覽器中應該可以看到按鈕的樣式沒有被移除,但是 CSS 的檔案大小增加為 40.6 kB,如果仔細看看這個檔案中的內容會發現裡面多了不少沒有使用到的 CSS,也許這個 extractor 還有調整的空間。

樣式正確,但檔案大小差強人意

小結

在這篇文章中我們瞭解了如何在 Next.js 使用 Ant Design 與 Tailwind,而搭配 purge CSS 可以有效地降低 CSS 的檔案大小,將檔案大小從 380 kB 降低為 40.6 kB。然而,以上是的設定是根據 purge CSS 的官方文件所設定的,不知道還有沒有更好的 extractor,可以更準確地移除無用的 CSS。

Reference


上一篇
Day25 - 如何在 Next.js 中正確地使用 lodash,使用 babel-plugin-import
下一篇
Day27 - 在 Next.js 如何正確地使用 dynamic import
系列文
從零開始學習 Next.js30

1 則留言

0
Dylan
iT邦新手 5 級 ‧ 2021-10-12 08:48:41

似乎貼到昨天的文了@@

Leochiu iT邦新手 5 級 ‧ 2021-10-12 19:20:38 檢舉

感謝提醒,居然會犯這種錯 /images/emoticon/emoticon02.gif

我要留言

立即登入留言