可扩展 CSS 的演变

大家好,我是 CUGGZ。
自 Web 诞生以来,我们编写和思考 CSS 的方式发生了巨大变化。从基于 table 的布局到响应式网页设计,已经走过了漫长的道路,现在进入了由现代 CSS 功能提供支持的自适应布局的新时代。而管理和组织 CSS 一直具有挑战性,很难达成共识。

深入研究在大型项目上扩展 CSS 的问题,可以加深对 CSS 的理解。我们将了解随着时间的推移出现和改变的各种 CSS 最佳实践的演变。最后,我们将很好地掌握过去在大型项目上扩展 CSS 的方法,以及 Tailwind 和其他一系列流行工具如何以反直觉的方式解决这些问题。

CSS 出现之前
一开始,Web 只有 HTML。我们将其全部大写,并直接在 HTML 上使用属性来设置页面样式:

<BODY>
  <P SIZE="8" COLOR="RED">LOUD NOISES</P>
</BODY>
这是一个黑暗的时期,除了可用的样式数量有限之外,一个明显的限制就是需要不断地重复。当时一个典型例子是旧的 Space Jam 网站。

现在有些人可能会想“这看起来像是传递到组件中的 props”。正如我们稍后将看到的那样,事情往往会随着创新周期的转折而全面发展。

样式表和关注点分离
CSS 的出现就可以消除所有这些重复。样式表使我们能够以声明方式设置页面样式,用很少的代码即可影响大量元素:

p {
  color: red;
}
现在可以分别考虑内容的结构及其视觉外观和布局。将布局问题从使用 table 的 HTML 转移到了 CSS 中。

随着时间的推移,开始在一个名为 CSS Zen Garden 的网站上收集示例。CSS Zen garden 成为展示人们如何创造性地使用 CSS 的中心。人们可以以有趣和独特的方式提交重新设计了相同 HTML 样式的 CSS 文件。

这对于传播将内容与其样式、可重新设置样式的 HTML 以及将可主题化的“皮肤”应用于核心框架的想法等方面非常有影响力。

寻找最佳实践
我们开始构建更复杂的站点和应用时,对 CSS 提出了新的要求。在最佳实践出现之前,任何新技术通常会在几个周期采用不同的方法。

Less 和 Sass 等工具的出现,扩展了原生 CSS 的功能。为我们提供诸如变量和计算函数之类的功能,极大地改善了开发人员的体验。

随着在这些样式表上花费更多的时间,我们想方设法组织所有这些规则和选择器。

许多不同的扩展 CSS 的模式就出现了。这些试图在维护、性能和可读性之间取得平衡,被称为“CSS 架构”。

在深入研究这些架构之前,首先来了解为什么在大型项目中管理 CSS 会很快变得复杂。

为什么CSS在大型项目难以管理?
“大型”指的是多个方面的交集,包括人员、工具、流程和性能。

有效扩展需要谨慎管理复杂性的增长。因此,随着系统的发展,它仍然是可理解的、可变的和高效的。添加新代码的成本尽可能低,人们才有信心更改和删除旧代码。

级联(CSS中的C)起源于 Web 早期。浏览器可以将默认样式应用于这些新的电子文档。然后,文档作者可以提供自己的样式,这些样式可以被单个用户的首选项覆盖。然后文档作者可以提供他们自己的样式,这些样式可以被个人用户偏好覆盖。

使 CSS 强大的相同属性也使得在大型项目中难以实现这些扩展属性。特别是它的全局命名空间、级联规则和选择器特异性。

全局命名空间
如果谨慎使用,全局 CSS 命名空间会非常强大。但在大型项目中,它会带来很多问题。

当一切都是全局的时,任何事情都可能出乎意料地影响其他事情。要么是现在,要么是将来的某个时候,当事情发生变化时。

这很快就会成为问题。在其他语言中,没有将所有内容都放在全局命名空间中是有原因的。随着更多代码的添加,代码会变得越来越难以预测和维护。

值得注意的是,CSS 的 Cascade Layers 是一项全新的功能,可以帮助解决这个问题。

命名困难
使用 CSS 进行项目快速迭代时,创建一系列语义化类名通常会很烦琐。当我们试图将一堆信息压缩到一个精确的标签中时,想出有用的名字就很困难。当一切都是全局的时候,正确地处理这一点变得更加重要。

过早命名事物是过早抽象的一种形式。因为通常我们命名的东西仍然需要完全形成,而且还不能重复使用。

在前端,更改设计是很常见的,这些标签经常会过时,需要对样式和 HTML 进行重构。

重构CSS困难
设计和现代软件开发都是高度迭代的。这要求我们定期重新评估对正在解决的问题的理解。在代码中,这意味着随着我们的理解发生变化和巩固而进行重构。

重构可能具有挑战性,但它是一种久经考验的真实方法,可以根据实际需求而不是理论来获得更好的抽象。

在 CSS 中,重构相当困难。如果没有可靠的视觉回归测试,许多 CSS 错误都是“无声的”,很容易产生无法预料的错误和副作用。这导致了几个常见的场景:

1. 仅附加样式表
项目开始时样式表都是易于管理的。但是,新代码在经过几次迭代和错误修复后,通常会停留在文件的末尾。我们很难知道何时可以安全地更改或删除规则,所以在文件末尾的级联中覆盖了之前的内容。

这就是特异性之争的原因,我们可能都有过需要覆盖其他样式的经历。!important 的出现加重了维护负担。

2. 死代码
在实践中,我们经常重复使用相同的 CSS 属性。不断复制规则通常比承担在全局命名空间中重构大量 CSS 的风险要安全得多。

这通常会导致大量未使用的 CSS,很难知道是否有东西依赖它。这最终会导致 CSS 散布在各种文件中。

调试CSS困难
调试的很大一部分就是在脑海中模拟计算机所做的事。调试复杂的 CSS 是很困难的,因为在考虑代码顺序的同时要在心里计算级联和计算的最终规则。

特别是 CSS 在定位、对齐、层叠上下文、边距和高度方面的许多细微差别。如果没有系统的方法,常见的 CSS 调试工作流程通常会涉及调整一些值以查看会发生什么。

当处理无法控制的代码或特定于浏览器的错误时,这尤其具有挑战性。

用CSS架构控制复杂性
CSS 有一个简单的模型,但事情很容易很快变得混乱。我们最终开始寻求应用软件工程原理来帮助我们进行管理。这些架构更像是组织 CSS 文件及其规则和选择器的高级蓝图。下面来快速了解一些有影响力和流行的 CSS 架构及其主要思想。

OOCSS:面向对象的 CSS
OOCSS 区分了我们在实践中编写的不同类型的 CSS。进行页面布局的 CSS,以及为 HTML 设置主题或“皮肤”的 CSS,如颜色、字体等。

OOCSS 中的“对象”是一种可以抽象和重用的重复视觉模式,其思想就是识别常见的视觉模式,并将重复的代码块提取到可重用类中。

使用最广泛的 CSS 框架之一 Bootstrap 就使用了 OOCSS。

SMACSS:可扩展和模块化的 CSS
在实践中,大型单文件 CSS 文件会很快变得难以管理且难以调试。SMCASS 是对不同类型的 CSS 进行分类的指南,并且与 OOCSS 等方法兼容。其主要想法是获取所有类名,并将它们组织到单独的桶中,并为 CSS 文件提供一些结构。除了一些关于命名类的约定之外。

BEM:块、元素、修饰符
BEM 是一种模型,用于考虑如何将事物分解为组件、子元素以及各种离散状态。

BEM 最初创建于 Yandex,它提供了一个系统的命名约定,通过保持所有选择器扁平化(没有后代选择器)来避免特异性之争,其中每个被设置样式的元素都有自己的类名。

BEM 与 Sass 等流行的 CSS 预处理器很好地融合在一起,嵌套规则可以编译成扁平化的 CSS 选择器:

.nav {
  // 块样式
 &__link {
    // 依赖父块的元素样式
  &--active {
        // 修饰符样式
  }
 }
}
ITCSS:倒三角形
ITCSS 背后的主要思想之一是通过分层的角度来考虑样式表,以帮助控制级联。

ITCSS 类似于 CSS 的“元框架”,与其他方法兼容。它通过提供越来越具体的明确层次来控制一切不可预测地相互压倒的混乱。

“倒三角形”来自每个渐进层形成的倒金字塔形状。这是管理大型项目 CSS 文件的一种有影响力的方法。

Cube CSS
Cube CSS 与全局命名空间以及级联一起工作,它提供了一组定义明确的桶,用于对 CSS 进行分类。这些就构成了 Cube 的首字母缩写词:Composition、Utility、Block、Exception。这是一种松散的方法论,就像组织 CSS 的心智模型。






与 ITCSS 类似,它是一个有影响力的“元 CSS 框架”,兼容各种方法。

重新思考关注点分离
随着 SPA 和组件驱动开发的兴起,我们开始看到 CSS 的新方法。

在这个世界上,管理 CSS 变得更加困难,因为组件现在是异步加载的,无法保证源顺序。一个常见的问题是,当执行从页面 A 到 B 的 SPA 转换时,页面上的某些元素看起来有所不同,但如果直接加载到 B 则看起来很好。导致一些有趣的调试会话。

我们开始寻找更具体的解决方案来管理 CSS,这些解决方案与这种以组件为中心的新方法结合在一起来构建我们的前端。

这些工具通常会打破我们迄今为止一直在建立和思考的许多既定的最佳实践。下面来了解一下。

行内样式
基于组件的框架中通常会在组件内部应用内联样式。在像 React 这样的框架中,可以将一个 JavaScript 对象传递给 style 属性,就会将其转换为内联样式。

这会引起许多人的本能反应,因为这就像我们要回到没有外部样式表的起点,抛弃了现有的最佳实践。

在组件的上下文中,内联样式不会面临最初的大量重复问题,因为它被封装在组件内部。样式只影响它们所在的元素,这是在组件中安全地添加和修改 CSS 的好方法。

内联样式的主要问题就是无法使用更强大的 CSS 功能,例如伪类选择器和媒体查询。

CSS in JS
在 React 早期,Veujx 就 Facebook 的 CSS 方法发表了演讲。从表面上看,这看起来很像内联样式,但可以访问样式表的强大功能。这次演讲使得采用 JavaScript 驱动的 CSS 方法的开源库激增。

第一波 CSS in JS 库因为 Styled Components、Emotion 等库而流行。它们解决了原生 CSS 在使用组件的大型项目中遇到的很多问题,使得处理 JavaScript 中的动态值变得非常容易。

CSS in JS 存在服务器端渲染效率低下、缓存问题和客户端运行时成本问题。这加剧了缓慢的应用启动时间,一旦 JavaScript 水合就需要多次重新渲染。

最近的第二波 CSS in JS 库旨在在不增加运行时成本的情况下为开发人员提供最佳体验。Linaria、Compiled、Vanilla extract 等工具可以在编译中从组件中提取样式表。这将在用户浏览器运行时发生的大部分事情转移到了编译时。

CSS 通常被编译成 Atomic CSS 以避免 CSS 文件臃肿,并且与动态运行时样式表相比更容易缓存。

CSS Modules
CSS Modules 可以在编写常规 CSS(或 Sass)和满足正在寻找的许多扩展属性之间取得平衡。

CSS Modules 允许我们使用 CSS 的全部功能,而不必担心样式在组件中溢出,同时将内容保持在组件目录中。

特别是在第一波 CSS in JS 库的浪潮中,将 CSS 绑定到特定的库对一些人来说太过分了,CSS Modules 就是一个很好的选择。然而,有些人可能认为这是 CSS in JS 的一种形式,因为它依赖于像 Webpack 这样的打包工具来生成并确保选择器的作用域。

无论如何,CSS Modules 是常规 CSS 世界和完全以组件为中心的方法(如 CSS in JS)之间的一个很好的中间方法。不过,仍然需要提供名称并与 BEM 等约定兼容。

具有挑战性的CSS最佳实践
同时,在基于组件的 SPA 世界之外,最初的 CSS Zen Garden 影响最佳实践在另一个方面受到挑战。

Atomic CSS 诞生于在大型项目上管理 CSS 的黑暗中。它最初的动机就是启用样式而无需编辑或将规则附加到现有得样式表。避免随之而来的所有问题。

与 OOCSS、BEM 和 SMACSS 等其他 CSS 架构相比,Atomic CSS 完全违反直觉,它比“块”和“对象”低一级,专注于单一用途的原子。直接违背既定的最佳实践,甚至在 HTML 规范中概述了如何不命名 CSS 类。

对于感觉修改现有 CSS 风险太大的项目团队,它已成为一种流行的提高生产力的方法。一些使用 Atomic CSS 的流行 CSS 库包括 ACSS、Tachyons、WindiCSS 等。

根据 state of CSS,这种 CSS 架构最流行的实现之一就是 Tailwind CSS 框架。

Tailwind 的崛起
自 2017 年发布以来,Tailwind 迅速受到欢迎。Tailwind的一个典型证明是,它通过使CSS更容易被非专家使用而提高了生产力,同时也带来了更易于维护的 CSS。

Tailwind 的基本原则
为了了解它为什么如此受欢迎,下面来研究一下 Tailwind 方法背后的基本原则。尽管看似抛弃了既定的最佳实践,但下面将介绍一系列其在实践中行之有效的实用原则。

减少命名
不必不断地为事物命名是 Tailwind 感觉如此高效的原因之一。这一工作流程是由自下而上组成单一用途原子的想法支持的。从可维护性的角度来看,这是避免仓促抽象的好方法。

从不命名任何东西会影响代码的可读性。通常会导致一大堆没有明确界限的原子类(或组件)。不过,在经常更改或与许多人一起更改的代码库中,这通常是正确的权衡。

适时抽象
Tailwind 提供了两种在适当的时候进行抽象的技术,一种是创建一个共享的 CSS 类来表示一个块(类似于 OOCSS)。或者在使用基于组件的框架时,更鼓励的做法是将重复的类提取到可重用(React、Vue、Solid、Svelte 等)组件中并共享它。

有信心重构
因为类是根据它们所在的 HTML 进行本地化的,所以可以放心地重构这些类,而不必担心影响其他元素或其他地方的组件。

这适用于作为文档的 Web 心智模型和以组件为中心的模型。这导致了一种感觉,即 Tailwind 可以根据正在构建的站点或应用的类型进行扩展。

避免死代码
预编译为 Atomic CSS 的 Tailwind 和 CSS in JS 解决了充满重复规则的臃肿 CSS 文件的问题。

使用 Atomic CSS,CSS 的增长与使用的样式数量相关,而不是开发人员提供的功能数量。例如,到处重用某些属性(如 flex)是很常见的。与其在不同类名下的样式表中复制这些内容,不如只写一次。对于每个属性/值的组合都是如此。

缩小设计差距
需要记住,CSS 最终是关于实现视觉设计的。许多开发人员觉得 CSS 很难的一个重要原因是设计很难。掌握正确的基础知识会大有帮助。在视觉设计中,一些关键元素是对齐、间距、一致性、大小、排版和颜色。

在 CSS 中,对于任何给定的属性,如 font-size、color 或者 padding,都有多种方法来实现值。通常会使用某一种方式来实现,这导致字体大小、间距和颜色不一致的细微差别激增,最终形成了一种粗糙的外观和感觉。

扩展 CSS 的一个关键部分就是通过具有可共享原语的坚实基础来弥合这一设计差距,这些原语定义了间距、字体大小、颜色值。这些通常被称为 Design Tokens,并构成设计系统的基础。没有这些基础,事情会变得非常随意和混乱。

Tailwind 受欢迎的一个关键方面是提供了一组可以开箱即用的预先考虑好的基础设计原语。这消除了大量的决策,这些决策通常是临时完成的,且不一致。

结语
没有工具是完美的,每个项目和团队都是不同的。无论采用何种方法,建立缩小设计差距的基础都是扩展 CSS 的关键要素。

专注于在其之上组合和构建的原语也有很长的路要走。这也适用于使用组件库的基于组件的大型应用。提供可组合的组件布局原语,如 Box、Stack、Inline 等是管理 CSS 的好方法,开发人员无需编写任何 CSS。

最近,Evergeen 浏览器推出了大量功能,解决了许多使 CSS 难以扩展的痛点。级联层、容器查询、子网格、has 等新功能可能会改变我们在未来思考和利用 CSS 的方式。

扩展CSS的成功与其说是对特定原则或最佳实践的教条式坚持,不如说是基于现实世界的约束来定义你需要什么,并以可持续和高效的方式来完成工作。

原文:https://frontendmastery.com/posts/the-evolution-of-scalable-css/

作者:CUGGZ


欢迎关注微信公众号 :前端充电宝