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


<<:  Day 26: Insertion sort & Selection sort

>>:  Day 26 战斗民族-俄罗斯酸奶牛肉 Beef Stroganoff

【Day19】传值和传址(传参考)

传值(Call by value) 在 JavaScript 中,只有原始型别为传值(Call by...

Day 06:「爱排队的兔兔有红萝卜!」- Flex 与 Grid

昨天被讨薪水,今天还在躲 ... 唉, 所以今天就不收作业了。 (兔导今天回学校兼课,带即将毕业的...

LeetCode解题 Day07

206. Reverse Linked List https://leetcode.com/prob...

DAY08 资料前处理-缺失值处理方法

前面我们介绍了如何使用探索性分析(EDA)来观察资料的型态,也学会用图表来找出这些资料的潜在讯息,今...

【Day2】变数宣告var、let、const的区别

但是老师教我用var宣告变数,但我也看到有同学用 let 与 const 宣告变数,这是怎麽一回事...