Tailwind CSS 是一个工具集 CSS 框架,网上很多文章已对其有详尽的介绍。本文不是官方文档的复述,也不是系列优点的罗列,作者 Gerard 会从另一个角度出发,在尽力保持客观的前提下,立足于实际开发的场景,指出 Tailwind CSS 存在的一些问题。事实上,除了文中提及的,Tailwind CSS 还存在着不少缺点,比如对高度定制化的支持程度不足、记忆大量预定义类名带来的心智负担等。友情提醒,你不一定会赞同这篇文章的看法,因为我们的看法会受到自身认知和使用体验的影响,但更重要的是可能是作者对新兴技术的态度,用他的原话说,就是:“When everyone is shouting that it’s awesome, it’s usually a good moment to sit down and have a good look at it”

下面正文开始。

引言

在过去的两年间,Tailwind CSS 广受青睐(从每周 30K 的下载量到如今的 600K)。毫无疑问,这个流行的实用优先 CSS 框架具备诸多优点。很可能你对它的惊艳和强大早有耳闻,因为很多开发者正是这么想的。

但关于这个框架,我们还有很多要说的。

什么是 Tailwind CSS?

如果你从来没见过 Tailwind 的实际应用,可以看这个:

<div class="bg-gray-100 rounded-xl p-8">Hello World</div>

这里的类名就反映了 Tailwind 的定义:一个包含多个预定义类(所谓的工具类)的集合。你并不需要编写基础的 CSS 样式规则,只需要直接在 HTML 中应用已经事先定义好的类名。

这样的类名还有很多。下面这个列表展示了部分类别和对应的例子:

  • 背景 (bg-gray-200, bg-gradient-to-bl)
  • 弹性布局 (flex-1, flex-row)
  • 网格布局 (grid-cols-1, col-span-4)
  • 内边距 (p-0, p-1)
  • 尺寸 (w-1, h-1)

之前有人将这个预定义类的集合比作可以在代码中使用的“乐高积木”。当然,它与传统的 CSS 有很多重复之处,但却不止于此:比如它还包括了预定义的范围(bg-red-100, bg-red-200 等)。Tailwind 旨在让我们的开发事半功倍,从某个角度来说,它也确实做到了。

同时,我很喜欢这个名字:Tailwind(顺风)。我闲下来的时候会驾船出海,顺风可是个不错的东西。

语法

正如上面所展示的,我们直接在 HTML 中书写工具类名。我们会很快想到这和内联 CSS 是非常相似的,这或许也能解释为什么 Tailwind 的开发者会在文档的开头部分就提及这个问题。

虽然他们极力解释,称 Tailwind 瑕不掩瑜(我不否认它确实有诸多优点),但我还是不太认可它的语法。我不想用一大堆类名污染 HTML 结构中的每一个元素,也不想每天都面对这样的代码:

Image for post

注意:上面这段代码来自 Tailwind 的文档,所做的事情是渲染一个简单的卡片。事实上,它最后呈现的效果非常漂亮,甚至还是响应式的。但如果放眼于我们的日常开发,这种情况就会急速恶化:如果我正在开发一个比卡片复杂更多的组件呢?如果我必须遵循设计师提出的某种设计风格,以及忍受他的一些“小怪癖”呢?

我尝试去应付这种情况,结果也在意料之中 —— 每一个 HTML 元素都充斥着一大堆 Tailwind 的工具类名。官方文档和教学视频并不会告诉你这些,但在我们着手开发大型应用的时候,这会是一个真实摆在我们面前的问题:

// 一大堆类名
<div class="sm:w-4 md:w-6 lg:w-10 xl:w-full sm:text-sm md:text-base lg:text-base xl:text-2xl flex-1 sm:flex-none bg-black sm:bg-white rounded-md sm:rounded-none">Hello World</div>

这也是实际开发中不可避免的。

上面这个例子可不夸张,我甚至可以说它是一个最简化的例子了 —— 至少对于那些有明确要求、明确设计风格(基于不同屏幕尺寸作出的响应式变化和样式调整)的应用来说,是这样的。

那么要怎么组织这些类名呢?也许我们要创建并遵循某个排序规则,但这样实在太复杂了。另一种做法是允许模板设计者和开发者使用任意一种具体的排序,但这样一来,为了找到要修改的目标类名。我们就不得不水平扫视甚至是滚动查看代码。

我可不想像找威利一样去找元素的字号(译者注:威利是儿童书籍《威利在哪里》中的人物,读者需要在一张人山人海的图片中找到威利)

我的观点是,部分 HTML 元素会使用非常多的样式,这种情况下应该考虑将样式与 HTML 标签进行分离,单独放到某个文件里。这样,我们就可以组织样式并增强其可读性。你不能把 CSS 的所有功能”塞到“ class 这一个 HTML 标签属性里,Tailwind 也不能。这样做只会让 HTML 结构越发臃肿。

@apply

针对上面提到的问题,Tailwind 允许我们在单个 CSS 文件中使用它们的类名:

.header {
  @apply bg-red-200 w-4 text-gray-400 rounded-sm border-red-400 border-2;
}

但比起传统编写 CSS(或者 SASS 等其它预处理器)的方式,我看不出这样做有什么优点。你可以说我是“老古董”,但我确实更喜欢下面这种编写方式:

.header {
  background-color: #FECACA;
  width: 200px;
  color: #444;
  border-radius: 5px;
  border: 2px solid #F87171;
}

再次强调,在真实开发中,元素可能会应用非常多的样式。

我并没有对 Tailwind 的优点避而不谈,其提供的部分工具类一定有更多用处亟待探索。但谈及语法的时候,我还是希望标记语言(HTML) 和样式规则可以进行明确的分离。我想,这是一个主观的看法。

清除无用代码

在项目中引入 Tailwind 之后,所有的类名都是可用的。但在构建和打包项目的时候,我们显然并不需要用上所有类名。因此,Tailwind 使用了 PurgeCSS 这个工具:

这就是 PurgeCSS 发挥作用的地方。PurgeCSS 会分析你的内容和 css 文件,首先它将 css 文件中使用的选择器与内容文件中的选择器进行匹配,然后它会从 css 中删除未使用的选择器,从而生成更小的 css 文件。

简单总结一下:首先,我们为项目引入大量的工具类名,接着,在准备构建并发布项目的时候,使用一个工具扫描代码并找出所有未使用的类名,以确保它们不会随其它代码一起打包。其实现依赖于下面这个正则表达式:

Image for post

引入并使用 Tailwind 会给我们的项目平添一层复杂性,复杂性带来的是一定的风险,会给我们的开发造成麻烦。比如说:

render(
  myItems.map(item => (
    <div className={`item level-${item.level}`}>
      {item.text}
    </div>
  ));
);

像这样动态生成类名的操作是做不到的。这意味着 Tailwind 对我们的开发造成了限制。关于这一点,文档也有提到,但很容易被开发者忽略:

Image for post

字符串拼接的操作是不允许的。

开发上的限制是一方面,还有一个问题是:给项目增加一层复杂性,通常会给项目带来风险。

这最终变成了一个关乎判断的问题,即 Tailwind 是否利大于弊?项目不同,对这个问题的回答也不同,但我们至少得留意到它存在的问题。关于 Tailwind 带来的限制性,上面提到的问题只是冰山一角。可以再举一个例子,那就是给 Tailwind 项目添加额外的(自定义的)CSS 并不那么简单直接

替代品

在阅读了 Tailwind 的文档并上手开发了几天之后,我忍不住在想:作者并没有意识到我们中的大多数人已经在日常开发中使用其它工具来简化样式编写了。

文档说过,使用 Tailwind 的一个好处在于可以避免魔数(译者注:魔数指的是缺乏解释或命名的独特数值,出现多次,且可以被有名字的常量取代)。确实如此,这是它的一个优点:我们定义一个诸如 bg-red-200 的颜色工具类,之后可以在代码各处使用,并在一个地方(Tailwind 的配置文件)集中修改它的实际值。这还是挺香的,我相信你也同意这种做法。

但今天的工具,比如说 SASS (周下载量超过五百万),早就可以轻松创建工具类和变量并在代码中重用了。甚至原生的 CSS 也已经支持使用变量。

当我们使用 SASS 或者原生 CSS 的时候,我们不需要面对额外的一层复杂性,在编写 CSS 样式规则的时候,也不需要改变既已形成的习惯和语法。

使用 Tailwind 是有成本的。花费时间和精力学习 Tailwind 的语法和类名,你会逐渐忘记其背后的语法:也即原生 CSS 的语法。如果我的开发者在一个更大的项目中使用 Tailwind 长达一年,他们将会逐渐忘记原生 CSS。这种事态真的乐观吗?我不太确定。

后序

Tailwind 很流行,它的吸引力和追捧者与日俱增。我能理解这其中的原因,毕竟使用它真的可以让我们受益匪浅。对其优点我也表示认可,它的一些工具类可以发挥很大的作用。

我撰写本文的目的,在于向各位展示一个事实:故事总是有两面性的。

一些人会从这个框架中受益,但还有一些人则会受限,他们会在开发的过程中不断发现这些限制 —— 或者更糟,在开发后才发现。

在适应新框架的时候,请保持你的批判性。当“每个人”都高声惊呼其惊艳的时候,也许正是冷静坐下、仔细端详的最佳时机。

感谢你花费时间阅读本文!