原子化 CSS 框架 Tailwind 初探

什么是 Tailwind

官网地址:一个实用性优先的 CSS 框架(即内置类似于 flex,pt-4 等各种类),且无需离开 HTML 即可快速构建现代网站。

Tailwind 预置了许多细粒度的 class,这些细粒度的 class 简单且单一,叫做原子 class,我们可以直接在 html 中使用行内样式的形式引入这些原子化的 class。

Tailwind 的特点/优缺点

  • 不用浪费精力发明类名/不用在 style 和 template 的上下文中切来切去/做出改变感觉更安全/模块复用/解决 CSS 优先级问题

相信每一个为 CSS 类尝试取一个完美的抽象的名词的前端人都感受到过 CSS 类取名时的苦恼,于是我们有了像 BEM 这种命名规范。那么不命名就是最好的命名,我们使用 Tailwind 时,写一些简单的样式,完全不需要考虑命名的问题,也不需要在 style 和 template 的上下文中来回切换。

BEM 命名规约是 .block-name__element-name--modifier-name,即 .模块名__元素名--修饰器名,组件库内此命名方式很常见。比如 .menu__item--active。

并且,CSS 是全局的,在修改一个类时,可能会有无法预期的改变(但是在我们工程化领域有 CSS 模块化,这种情况少见),原子化 CSS 是直接作用于单个 HTML 元素,可以随意更改它们而不用担心无法预期到的改变。

模块复用,意思是可以直接复制一段 html 到另外的文件,而不用担心它附着的 CSS 样式。

解决 CSS 优先级问题:因为直接作用原子化样式于某一元素中,所以确实可以很好的解决优先级的问题。


  • 这不是就是内联样式吗

对于内联样式,官方是这样回答的:

  1. 有约束的设计。

使用内联样式,每一个值都是 a magic number,借助从预定义的设计系统中选择样式,使构造视觉上一致的 UI 变的更加容易。

  1. 方便的使用响应式设计/悬停、焦点和其他的一些状态。
xml 复制代码
<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">
<!-- default bg-sky-500 hover bg-sky-700 -->
<button class="bg-sky-500 hover:bg-sky-700 ...">
  Save changes
</button>

  • 默认采用JIT引擎/按需打包,减少 CSS 包体积

Tailwind 在 2021 年 12 月 发布了他们的最新的一个大版本 V3.0,对 V2 进行了少量的重大更改,比较重大的更新就是对于按需打包的支持。

在较低版本的 Tailwind 中,若没有做任何配置,可能由 Tailwind 输出的 css 体积会变得惊人的大:477.6kb(未压缩),并且在本地进行初始化编译构建时,会消耗数十秒甚至更多的时间,这是因为每次都是全量进行样式的构建的。

于是 Just-In-Time 引擎的出现解决了这个问题。通过配置 mode 模式为 jit,和 purge 路径,Tailwind 会在开发和生产的使用一致的行为,进行按需生产出 css 样式。

js 复制代码
// tailwind.config.js
module.exports = {
  // Old Version Config
  mode: 'jit',
  // 需要做 按需加载的 文件路径 
  purge: [
    './public/**/*.html',
    './src/**/*.{js,jsx,ts,tsx,vue}',
  ],
  // New Version Conifg
  content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,vue}'],
  // ...
}

在 Tailwind 新版本中,采用 JIT 引擎取代旧引擎。直接删去 mode 和 purge 选项,用 content 选项代替,content 选项告诉 Tailwind 要扫描哪些文件进行按需加载,必须要配置 content 选项,否则可能最后生成的 css 为空。

对于 CSS 样式体积,项目打包出来样式代码占比将突然骤降然后趋于不变。项目中的 CSS 文件体积并不会随着项目代码的增多而线性增长。就是说,之前你实现的功能越多,你的 CSS 文件体积也会越大。但是用了 Tailwind 之后,这个关系更像是一个刚开始趋于线性然后缓慢增长的曲线。即在初期的时候 CSS 文件体积上升速度会比较快,但是随着项目功能越来越多。你的 CSS 文件体积膨胀速度会越来越慢,因为复用的越来越多了嘛。

关于 CSS 样式体积优化这一点,Tailwind 的重构让 Facebook 的主页减少了 80% 的 CSS 体积,从旧网站仅登录页就有 413 kb CSS 到整个页面 CSS 只有 74 kb [ 1 ] 。 也有其他人对 Tailwind 的体积优化做了一些测试 [ 2 ] ,但局限于项目CSS代码量级的原因,并没有什么效果。总的来说,当项目属于巨石项目时,CSS 的优化还是比较客观的,但是当代码量比较小时,可能作用并不大。


  • 切换成本太高了,我不想做出改变/可读性太差

有人会说,这样写 CSS 岂不是要记很多的样式吗,难道我每次都要去查文档?对于一些常见的样式,比如 width,height,color 这种,其实只要写过一次就能记住,并且对于这些样式,都是有规律可循的,对于一些稍微复杂的,我们有官方 VSCode 插件 Tailwind CSS IntelliSense 来帮助我们快速开发。

配置 插件 JSON 让提示更加迅速

json 复制代码
"editor.quickSuggestions": {
  "strings": true
}

关于可读性,这个见仁见智。这里能从两种可读性来说,一种是类名的可读性,也就是语义性,如果对一个 class 名使用 BEM 起名,我们大致能知道这个类它所表达的意思,但是它也不能完全展示出这个类具体的所展示的样式(我们还需要定位到具体的类看它的实现是怎么写的),但是 Tailwind 一眼望过去,你也可能完全不懂这是在写什么,但是省去了你切换文件的时间。另一种,就是 HTML 里的类名太长了,导致 HTML 的看起来的代码可读性非常差,甚至是辣眼睛。

例如下面的:

less 复制代码
// case:Tailwind
<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">
  给我眼睛看花了
</button>

这样的情况,有类似于 prettier-plugin-tailwindcss 的插件,可以规范 class 的顺序,比如 margin 在先,padding 在后。Tailwind 推荐的做法是将此种过长的 class 利用 @apply 函数进行抽离。

而另外一款原子化框架 Uno CSS [ 3 ] 大大优化了这一点,它能按更好的按类型进行组织。

html 复制代码
// case:UnoCSS
<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  好点了
</button>

Tailwind 快速尝试及实用技巧

快速尝试: 直接使用 Tailwind CLI 或 利用现有的框架、如 Vite,情况都类似

  1. 安装依赖

npm install -D tailwindcss postcss autoprefixer

  1. 利用 CLI init Config 文件

npx tailwindcss init

  1. 在配置文件 tailwind.config.js 添加需要进行转换的模版路径
  1. 在你的主 CSS 文件中通过 @tailwind 指令添加每一个 Tailwind 功能模块。

或直接引入 import "tailwindcss/tailwind.css" 包的内容,该包下包含了上面的内容

tailwindcss 全部都是一级样式,在类名权重相等的情况,下面的样式可以覆盖上面的样式,所以工具类优先,组件类次之,基础样式兜底,生成的样式顺序尤为重要,所以上面三句指令的顺序非必须建议不要修改。

  1. 运行打包

CLI 下:npx tailwindcss -i ./src/input.css -o ./dist/output.css --watch

或 Vite下:npm run dev,Vite 下作为一个 PostCSS 的插件,在打包中会自动运行

实用技巧

  • 在类型较长或可抽象复用的时候,善用指令
less 复制代码
@tailwind base;

@tailwind components;

@tailwind utilities;

@layer components{
  .hzhBtn {
    @apply text-red p-l-6 p-r-6;
  }
}
// 或者像
@layer utilities{
  .flex-center {
    @apply flex items-center justify-center;
  }
}
// 按照顺序 将 .hzhBtn 生成到 components 桶后面
// 按照顺序 将 .flex-center 生成到 utilities 桶后面
// 所以必要时,.flex-center .hzhBtn 作用于同一html时, 
// 实用工具类仍然可以覆盖组件类
  • 在跨项目想复用样式时,可以将样式抽成 Tailwind 包,再进行跨项目导入复用

Tailwind 运行的原理

Tailwind 本身就是一个 PostCSS 的插件,和 Vite 搭配使用时,由于 Vite 能良好的支持PostCSS,只需要把 Tailwind 作为 PostCSS 的一个插件即可使用 TailwindCss。

比如,这里还有一些其他的 PostCSS 插件:

autoprefixer:为 CSS 选择器增加不同的浏览器前缀

cssnano:压缩 CSS 文件

postcss-import:通过 @import,整合多个 CSS 文件

stylelint:配置不同的规则,可以实现 CSS 的静态语法校验。

PostCSS 是当今自动化 CSS 处理和优化的流行选择。相较于我们使用的比较多的 CSS 预处理器 Sass、Less 等,还是有一定的区别。像 Sass 预处理器,提供一些 API 让我们能像语言一样对 CSS 进行处理(变量、函数、循环),而 PostCSS 于 CSS 就像 Babel 于 JS,两者都是特点语言的处理器,将某种语言进行转译。PostCSS 对于 CSS 的处理流程类似于 Babel,即 parse(对源码进行处理,生成 AST)、transform(对 AST 进行增删查改)、generate(通过 AST 输出结果字符串),在过程中 暴露不同的 API 并使用不同的 Plugin。

对于 PostCSS,其实它强大的能力已经足够覆盖预处理器的功能。但是一般来说,预处理器可以让我们使用一些各式类似编程的方式写 CSS,编程成 CSS 后,再由 PostCSS 这个后处理器对 CSS 进行最后的处理。因为 PostCSS 要求输入和输出均为 CSS。

并且 PostCSS 是基于 Node 的,可以使用 JavaScript 本身及其繁荣的生态。像 Sass,底层是基于 Ruby 语言的,所以我们使用 Sass 时还需要 node-sass 这样的库帮我们进行链接。

你有过被 node-sass 版本各种不对应所经历过的折磨吗?

astexplorer 中可以PostCSS 对 CSS 处理后生成的 AST。

对于 PostCSS,比如 start-kit-builder,使用的是 webpack 打包,对于 CSS 的处理,使用了 optimize-css-assets-webpack-plugin 这个 Plugin,这个 Plugin,同样也是利用了 PostCSS 提供底层能力,cssnano[4] 作为 PostCSS 的插件来处理 CSS(cssnano 是该 plugin 默认选择的 PostCSS 插件,没有传入 cssProcessor 字段参数时会默认导入它)。

而对于 Tailwind,Tailwind 作为 PostCSS 的一个插件,在拿到 AST 后,通过拿到用户传入 Content 中的文件路径,并拿到类似于 @layer @apply 这些拓展的指令,对CSS AST Tree 进行增删改查。再通过正则匹配到模版中的一些原子类动态按需的生成原子 CSS。

Tailwind zero-runtime,是因为 Tailwind 在编译时就将类处理好了。这就是为什么 Tailwind 为什么不允许通过动态构造类名,因为 动态构造类名是在运行时进行的,Tailwind 无法静态识别。

css 复制代码
/** 不会生产 text-red-600 或 text-green-600 的颜色 因为无法识别 **/
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
/** 要像这样 **/
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

参考

[1] 《atomic-css-in-js》- sebastienlorber.com/atomic-css-...

[2] 《原子化 CSS 真能减少体积吗》- juejin.cn/post/729501...

[3] Uno CSS - unocss.dev/

[4] cssnano - www.cssnano.cn/docs/introd...

相关推荐
brrdg_sefg12 分钟前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_7482309437 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681044 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
黑客老陈2 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安2 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235612 小时前
web 渗透学习指南——初学者防入狱篇
前端