Tailwind CSS 类名压缩:应用与局限性
注意: 本文中涉及 Tailwind CSS 使用的是 v3.x 版本
背景
去年,在正忙于一个用 Tailwind CSS 构建的项目时,我偶然看到了一篇让我兴奋的文章,标题赫然写着"通过压缩类名减少了 70% 的 CSS 体积"。这引发了我的兴趣,促使我寻找优化我的代码体积的可能性。
就像 Google 做的那样
有时候,我们可以从业内标杆公司那里汲取灵感。你是否曾经看过 google.com 的源代码吗?如果你仔细分析,你可能会注意到一些令人惊讶的细节,其中之一就是,CSS 类名不过几个字符的长度。这是一个巧妙的优化策略,可以极大地减少 CSS 文件的体积,从而加快页面加载速度。
css-loader 的局限
我开始尝试一些可用的工具来压缩我的 Tailwind CSS 代码,但是很快我发现,虽然像 css-loader 这样的工具可以帮助进行 CSS 代码的压缩,但它们大多数都无法正确地压缩 Tailwind CSS 的类名。这是因为 css-loader 只能用来压缩 CSS Modules 生成的类名。那么,如何解决这个问题呢?
unpluign-tailwindcss-shortener-plugin 的实现
这时,需要尝试一个针对 Tailwind CSS 的压缩工具,我实现了一个有效的解决方案 - unpluign-tailwindcss-shortener-plugin。它将 Tailwind CSS 的类名映射到较短的类名,从而实现了压缩的效果。
实现过程中发现的问题
静态类名:指在编写 HTML 或者模板文件时就已经确定的类名。这些类名并不会在运行时发生变化,代码中明确写出了具体的类名。
动态类名:在运行时根据某些条件动态生成或修改的类名。通常在 JavaScript 或前端框架(例如 React、Vue 等)的使用中,通过条件判断来动态赋予 HTML 元素不同的类名,从而实现样式的动态变化
- 处理静态类名实际上很简单,将 html、vue 的 template 以及 jsx 使用 AST 或者 正则找到类名替换它。
- 对于动态类名的话,比较麻烦。后面提出一个约定:
- 在 template、js、jsx 里,动态类名必须需要使用 cx、cva 包裹
- cx、cva (support class-variance-authority) 可以使用 class-variance-authority、classanems、clsx、tailwind-merge 等等用来 类名拼接的工具,你甚至可以自定义一个。但是名字必须是 cx 或者 cva。
应用后的效果
HTML
<!-- before -->
<div class="flex items-center justify-center h-screen px-6 bg-gray-200"></div>
<!-- after -->
<div class="h u bu i s j"></div>
JavaScrip
// before
import { cx } from "class-variance-authority";
const boxClass = cx(
"flex items-center justify-center h-screen px-6 bg-gray-200"
);
// after
import { cx } from "class-variance-authority";
const boxClass = cx("h u bu i s j");
CSS
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.h-screen {
height: 100vh;
}
.flex {
display: flex;
}
/* after */
.s {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.j {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.bu {
justify-content: center;
}
.u {
align-items: center;
}
.i {
height: 100vh;
}
.h {
display: flex;
}
使用
用开源项目 shadcn-admin (TailwindCSS + shadcn/ui) 作为例子。
- 拉取代码
git clone https://github.com/satnaing/shadcn-admin.git
- 下载依赖
pnpm i
pnpm i unplugin-tailwindcss-shortener
- 修改 vite.config.js 配置
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import TailwindcssShortener from "unplugin-tailwindcss-shortener/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
TailwindcssShortener({
tailwindCSS: "./src/index.css",
keyword: {
cva: true,
extra: "cn",
},
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
- 检查项目内类名是否符合规范
修改 sidebar.tsx,按照约定修改
// 代码较多,只列出部分代码
// 修改前
<div
onClick={() => setNavOpened(false)}
className={`absolute inset-0 transition-[opacity] delay-100 duration-700 ${navOpened ? 'h-svh opacity-50' : 'h-0 opacity-0'} w-full bg-black md:hidden`}
/>
// 修改后
<div
onClick={() => setNavOpened(false)}
className={cn('absolute inset-0 transition-[opacity] delay-100 duration-700 w-full bg-black md:hidden', navOpened ? 'h-svh opacity-50' : 'h-0 opacity-0')}
/>
- 类名冲突问题
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
const cssMap = (import.meta.env.TWST_CSS_MAP ?? {}) as Record<string, string>
const shortToOriginMap = new Map<string, string>(Object.entries(cssMap))
const originToShortMap = new Map<string, string>(
Object.entries(cssMap).map(([k, v]) => [v, k])
)
export function cn(...inputs: ClassValue[]) {
const classnames = clsx(inputs)
const before = classnames
.split(' ')
.map((classname) => {
const short = originToShortMap.get(classname)
if (short) {
return short
}
return classname
})
.join(' ')
const merged = twMerge(before);
const after = merged
.split(' ')
.map((classname) => {
const short = shortToOriginMap.get(classname)
if (short) {
return short
}
return classname
})
.join(' ')
return after
}
其他示例
类名压缩的局限和思考
一个不可忽视的事实是:尽管类名压缩确实能减少文件尺寸,但实际上可能对整体体积缩减的影响有限。一般情况下类名所占体积缩减可能在 70%左右,但实际上在多数项目中,类名体积在几十到一百字节之间,这意味着实际减少的仅是几十字节。
最重要的一点是只采用 Tailwind CSS 类似 CSS 原子化的项目,本身 CSS 文件就已经非常小。
最后普,为了减小CSS文件的体积,我们有多项策略可行,而类名压缩仅是众多方法中的一种。此外,我们还可以通过代码分割、应用gzip压缩等其他技术手段来实现相同的目标。
最后,文中若有不足或错误之处,敬请批评指正。感谢您的阅读和宝贵意见!