在前端的世界中,我們經常會站在巨人的肩膀上,如果任何事情都需要自己從零開始動手做,後續的維護也會耗費可觀的成本。例如想要一個好用的 UI framework,有 Material UI、Ant Design、Chakra 、 Bootstrap 等以上幾種常見的選擇;想要方便的 utility CSS framework,最熱門的選項不外乎是 Tailwind。
但是在設定這些 framework 時通常都需要引入整包的套件,像是我們只想要使用部分 Tailwind 的功能,但是明明很多功能都用不到,卻在客戶端載入異常龐大的 CSS 檔案,對於使用者的體驗也會不好。
在這篇文章中我們想要嘗試在 Next.js 中導入 Ant Design 與 Tailwind 兩個套件,然後在 Next.js 使用 postCSS 設定 purge CSS,達到在打包時移除沒用到的 CSS,避免客戶端必須載入無用的 CSS 。
在使用 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-color
跟 border-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-color
跟 border-radius-base
都被新的設定覆蓋,所以你在畫面上就可以看到修改預設 theme 後的元件。
import { NextPage } from "next";
import { Button } from "antd";
const Home: NextPage = () => {
return <Button type="primary">my button</Button>;
};
export default Home;
接下來我們要在 Next.js 中設定 Tailwind,在 Next.js v10 以上的版本要使用以下指令安裝 Tailwind:
yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
接著,再使用以下指令初始化 Tailwind 的環境,以下指令會在專案中產生 tailwind.config.js
與 postcss.config.js
兩個檔案:
npx tailwindcss init -p
從 Tailwind 官方網站中看到以上指令會在 postcss.config.js
中新增 tailwindcss
與 autoprefixer
兩個 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
成功地被設置,原本緊貼在螢幕邊緣的按鈕,現在多了一些間距:
想要測試真實環境下每個頁面會載入的 CSS 檔案大小,必須事先 yarn build
及 yarn start
,如此一來才能準確地看到 CSS 檔案被額外地載入,如果只是開啟 dev server,CSS 只會被插入在 JavaScript 裡面,無法準確地知道 CSS 檔案的大小。
在 yarn start
之後,從 Chrome → Network → CSS 中可以看到有一包 CSS 檔案被載入,從畫面中可以看到其大小為 380 kB,是一個不小的數字,明明只有用到一個 Ant Design 的按鈕元件, 也只有用到 Tailwind 的 m-8
這個 CSS selector 而已,理應是一個不大的檔案大小才對。
而且如果每次使用者進入到一個頁面都需要載入這麼大的檔案,對於網路傳輸較慢的使用者將會是一場折磨。
所以為了解決這個問題,我們需要使用 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 build
及 yarn 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 build
與 yarn 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。