【译】可扩展的 CSS 演变


自从网络诞生以来,我们编写和思考 CSS 的方式发生了显著变化。

从基于表格的布局,到响应式网页设计,再到现在由现代 CSS 特性驱动的自适应布局,我们已经走过了很长的路。

管理和组织 CSS 一直是一项挑战,很难达成共识。

在这篇文章中,我们将深入了解 CSS,探讨使其难以扩展的潜在问题。

我们将了解随着时间推移而出现和变化的各种 CSS 最佳实践的演变。

最后,我们将掌握在大型项目中扩展 CSS 的过去方法,以及流行工具如 Tailwind 和其他工具如何以反直觉的方式解决这些问题。

CSS 之前的时代

最初,网络上只有 HTML。我们以全大写书写,并通过直接在标记上使用属性来为页面设置样式:

1
2
3
<body>
<p SIZE="8" COLOR="RED">LOUD NOISES</p>
</body>

对于那些想要时尚页面的人来说,这是黑暗的时代。

除了有限的样式选项外,所有需要的重复也是一个明显的限制。

有些人可能会认为“这看起来像我传递给组件库的属性”。正如我们将看到的,在创新的周期中,事情往往会以一种扭曲的方式回到原点。

样式表和关注点分离

CSS 登场了,就像许多从以前痛点中诞生的创新一样,我们现在可以去除所有的重复内容了。

样式表使我们能够声明性地为页面设置样式,只需很少的代码就能影响大量元素:

1
2
3
p {
color: red;
}

我们现在可以分别考虑内容的结构及其视觉外观和布局。将布局问题从 HTML 的表格中移到了 CSS 中。

最初,CSS 非常简洁,受物理桌面出版的启发,如“float”等术语支持文本列的格式化。

随着时间的推移,我们开始在一个名为 CSS Zen Garden 的网站上收集示例。

CSS Zen Garden 成为展示人们如何创造性使用 CSS 的中心。人们可以提交 CSS 文件,以有趣和独特的方式重新设置相同的 HTML 样式。

这在传播将内容与样式分离、可重新样式化的 HTML 和为核心骨架应用主题“皮肤”的想法方面影响深远。

找出最佳实践

我们开始构建更复杂的网站和应用程序,提出了对 CSS 发展的新需求。

任何新技术通常需要经历几个不同方法的周期,才能产生最佳实践。

我们看到了像LessSass这样的工具,它们扩展了原生CSS的功能。给我们提供了变量和计算函数,大大改善了开发者体验。

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

出现了许多不同的模式来扩展 CSS。它们试图在维护性、性能和可读性之间找到平衡,被称为“CSS 架构”

在深入了解这些架构之前,让我们首先了解为什么在大型项目中管理 CSS 会迅速变得复杂。

为什么 CSS 难以大规模管理

“大规模”指的是多个方面的交集,包括人员、工具、流程和性能。

有效扩展要求我们仔细管理复杂性的增长。这样系统在扩展时仍然可以理解、可更改且性能良好。添加新代码的成本保持在尽可能低的水平,并且人们对更改和删除旧代码充满信心。

级联(CSS 中的 C)起源于网络的早期。浏览器可以对这些新电子文档应用默认样式。文档作者可以提供自己的样式,这些样式可以被用户个人偏好覆盖。

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

全局命名空间

如果小心利用,全局 CSS 命名空间可以很强大。但在大型项目中,它往往是一种诅咒。

当一切都是全局的,任何东西都可能意外影响其他内容。无论是现在还是将来发生变化时。

这很快就会成为问题。我们在其他语言中不将所有内容放在全局命名空间中是有原因的。随着代码的增加,事情变得不那么可预测和难以维护。

值得注意的是,CSS 级联层是一个即将推出的功能,可以本地解决这个问题。

命名事物很难

在快速迭代 CSS 时,创建一系列语义类名常常感觉像是没有太多好处的琐事。

想出有用的名称很难,因为我们试图将大量信息压缩到一个精确的标签中。当一切都是全局的时,正确命名变得更加重要。

过早命名事物是一种过早抽象。因为我们命名的事物往往还未完全成型,尚不可重用。

在前端,设计变更很常见,这些标签经常变得过时,需要重构样式和标记。

重构 CSS 很难

设计和现代软件开发高度迭代。通常只有在积累了几次迭代后,我们才开始形成清晰的认识。

这要求我们定期重新评估对所解决问题的理解。在代码中,这意味着随着理解的变化和巩固进行重构。

重构可能具有挑战性,但它是一种经过验证的方法,可以基于实际需求而不是理论逐步获得良好的抽象。

在 CSS 中,这相当困难。如果没有坚实的视觉回归测试,许多 CSS 错误是“无声的”,很容易造成不可预见的错误和副作用。这导致了几种常见的情况。

仅追加样式表

项目开始时,样式表感觉可管理。但经过几次迭代和错误修正后,新代码通常会被卡在文件末尾。

很难知道何时可以安全地更改或删除规则。因此,我们在文件末尾的级联中覆盖之前的内容。

这就是特异性战斗的原因。我们可能都有需要覆盖其他样式的经验。这是一条容易走入黑暗艺术的道路,那里开始出现 !important,加重了维护负担。

死代码

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

这通常会导致许多未使用的 CSS,这些 CSS 很难知道是否有依赖。最终导致CSS分布在各个文件中,变得臃肿。

调试 CSS 很难

调试的一个重要部分是在头脑中模拟计算机的工作。

调试复杂CSS很难,因为我们在脑海中计算级联和最终规则,同时考虑源顺序。

特别是 CSS 在定位、对齐、堆叠上下文、边距和高度方面的许多细微差别。没有系统的方法,常见的 CSS 调试工作流程往往涉及调整一些值看看会发生什么。刷新页面,结果完全没有变化。或者完全崩溃了。

这在处理你不控制的代码或特定浏览器的错误时尤其具有挑战性。

使用 CSS 架构驯服复杂性

CSS 有一个简单的模型,但事情很容易迅速变得混乱。最终我们开始寻求应用软件工程原则来帮助我们管理。

这些架构更像是组织CSS文件及其规则和选择器的高级蓝图。

让我们快速概述一些更有影响力和流行的CSS架构及其主要思想。

OOCSS:面向对象的 CSS

OOCSS 区分了我们在实践中编写的不同类型的 CSS。布局 CSS 和为 HTML 主题化或“皮肤”的 CSS,如颜色、字体等。

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

利用这些想法的最广泛使用的 CSS 框架之一是 Bootstrap

SMACSS:可扩展和模块化 CSS

在实践中,单个大型CSS文件很快变得不可管理且难以调试。

SMACSS 是一种将不同类型的CSS分类的指南,与OOCSS等方法兼容。

其主要思想是将所有这些类名组织成不同的类别,为我们的CSS文件提供一些急需的结构。以及一些命名类的约定。

BEM: Block, Element, Modifier

BEM 是一种将内容分解为组件、其子元素和各种离散状态的模型。

最初由 Yandex 创建,它提供了一种系统的命名约定,通过保持所有选择器平坦(无后代选择器),避免了特异性争斗,其中每个被样式化的元素都拥有自己的类名。

BEM 与流行的 CSS 预处理器(如 Sass)配合良好,嵌套规则会编译成平坦的 CSS 选择器:

1
2
3
4
5
6
7
8
9
.nav {
/* 块样式 */
&__link {
/* 依赖于父块的元素样式 */
&--active {
/* 修饰符样式 */
}
}
}

ITCSS:倒三角

ITCSS 的主要理念之一是通过分层的视角来思考样式表,以帮助驾驭级联。

ITCSS 是一种类似于“元框架”的 CSS,与其他系统兼容。

其理念是通过提供逐层增加的特异性显式层,驾驭一切互相不可预测地覆盖的混乱。

“倒三角”源于每一层递进的层形成倒金字塔形状。

这是在大规模项目中管理 CSS 文件的一个有影响力的方法论。如需深入了解,可以观看其创建者的演讲。

Cube CSS

Cube CSS 与全局命名空间和级联共存,而不是试图绕开它们。

Cube CSS 提供了一组明确定义的桶来分类 CSS。这些构成了 Cube 的首字母缩写:Composition(组合)、Utility(工具)、Block(块)、Exception(例外)。

文档很好地解释了这些原则。它是一种松散的方法论,就像是组织 CSS 的心智模型。

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

重新思考关注点分离

随着单页应用(SPA)和组件驱动开发的兴起,我们开始看到 CSS 的新方法。

在这个世界中,管理CSS变得更加困难,因为组件现在是异步加载的,没有关于源顺序的保证。

一个常见的问题是,当从页面 A 过渡到B时,页面上的某个元素看起来不同,但直接加载到B时看起来很好,导致一些有趣的调试过程。

我们开始寻找更具体的解决方案来管理与这种新组件中心方法相契合的 CSS

这些工具通常打破了我们一直在建立和思考的许多既定最佳实践。让我们来了解它们。

内联样式

组件化框架的转变常常看到样式在组件内应用内联。在 React 等框架中,我们将一个Javascript对象传递给style属性,将其转换为内联样式。

这对许多人来说是一种强烈的反应,因为这就像我们回到了没有外部样式表的起点,抛弃了现有的最佳实践。

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

内联样式的主要问题是缺乏对更强大 CSS 功能的访问,如伪选择器和媒体查询。此外,难以利用共享设计令牌、缓存、静态分析和预处理。

CSS in JS

React 早期,Veujx FacebookCSS方法发表了演讲。表面上看,这看起来很像内联样式,但具有样式表的强大功能。

这次演讲导致了一系列开源库的繁荣,它们采用 Javascript 驱动的 CSS 方法。

第一波 CSS in JS 库在 React 生态系统中变得流行,如 Styled ComponentsEmotion 等。

这些库解决了在使用组件的大型项目中原生 CSS 的大多数问题,使得使用 JS 的动态值变得非常容易。

问题是最终用户支付的性能代价。存在服务器端渲染效率低下、缓存问题和客户端运行时成本的问题。

这加剧了应用启动时间缓慢的问题,需要多次重新渲染才能在 Javascript 激活后完成。对于大型应用来说,工作量迅速增加。

最近的第二波 CSS in JS 库旨在在不增加运行时成本的情况下提供最佳的开发者体验。

工具如 Vanilla extractLinaria Compiled 在编译步骤中将样式表从组件中提取出来。这将很多在用户浏览器上运行时发生的事情转移到编译时。

CSS 通常被编译为原子 CSS(我们稍后会触及的一种CSS架构),以避免膨胀的 CSS 文件,并且与动态运行时样式表相比,缓存性更强。

CSS 模块

CSS 模块在编写常规 CSS(或 Sass)与实现我们所寻找的许多扩展属性之间取得了平衡。

CSS 模块允许您在不担心样式在组件之间扩散的情况下,使用 CSS 的全部功能和控制,同时将样式本地化在组件目录内。

特别是对于第一波 CSS in JS 库来说,将 CSS 与特定视图库绑定对某些人来说是一个过分的步骤,而 CSS 模块是一个很好的替代方案。然而,有些人可能认为这是一种 CSS in JS,因为它依赖于如Webpack之类的打包工具来生成和确保选择器的范围。

无论如何,CSS 模块是常规 CSS 世界与完全组件中心方法(如 CSS in JS)之间的一个很好的中间地带。仍然需要起名字,并且与 BEM 等约定兼容。

挑战 CSS 最佳实践

同时,在组件化SPA的世界之外,受 CSS Zen Garden 影响的最佳实践在另一个方面受到了挑战。

原子 CSS 诞生于管理大型项目的 CSS 的黑暗中,并受到其塑造。

它的初始动机完全是务实的——在不必编辑或附加现有样式表规则的情况下进行样式设计。避免所有与此相关的问题。

它在与 OOCSSBEMSMACSS 等其他 CSS 架构的对比中,完全是违反直觉的。

原子 CSS 低于“块”和“对象”一级,专注于单一用途的原子。直接违反HTML规范中关于如何命名CSS类的既定最佳实践。

它已成为团队在修改现有CSS感觉过于冒险的项目中提高生产力的一种流行方法。一些流行的CSS库包括 ACSSTachyonsWindiCSS 等。

根据 CSS 状态,今天最受欢迎的这种 CSS 架构的实现之一是 Tailwind CSS 框架。

Tailwind 的兴起

自 2017 年发布以来,Tailwind 一直迅速走红。Tailwind 的典型推荐是,通过使 CSS 对非专家更易访问,同时导致更可维护的 CSS 来提高生产力。常见的建议是“只需试试,你就不会回头”。

支持 Tailwind 的原则

要理解其受欢迎程度,我们需要检查 Tailwind 方法背后的基本原则。

尽管表面上似乎抛弃了既定的最佳实践,但我们会看到其背后是一些在实践中有效的务实原则。

延迟命名

不必不断地命名事物是 Tailwind 感觉如此高效的一个原因。这种工作流程是通过自下而上地组合单一用途的原子来支持的。

从可维护性的角度来看,这是避免过早抽象的好方法。

永远不命名事物会损害代码的可读性。通常会导致一堆没有明确边界的原子类(或组件)。

这是一个有效的批评。但在实践中,令人惊讶的是,在成为合法痛点之前,这可以走多远。在经常变更的代码库或有许多人参与的代码库中,这通常是正确的权衡。

这是在组件模型中一种强大的工作流程。您已经为正在处理的组件起了一个语义名称,因此您最终在此基础上构建样式,将样式局限于组件。

这是《构建面向未来的前端》中所述的相同高级原则。这不是关于永远不创建抽象,而是不要过早创建它们。

在合适的时间进行抽象

在迭代操作时,轻松删除和更改代码的能力远远超过了重复代码的成本。

编写 CSS 时的一个常见痛点是,前期使代码过于 DRY 或优化通常感觉像是一种浪费,因为以后很难更改。

将重复代码抽象为常见抽象要比绕过过度抽象代码并使其适应新用例要容易得多。

Tailwind 提供了两种在合适时间进行抽象的技术,要么通过创建一个表示块的共享 CSS 类(类似于 OOCSS),要么更鼓励使用基于组件的框架,将重复的类提取到一个可重用的组件中并共享。

有信心进行重构

因为类是局限

于它们所在的标记的,所以我们可以自信地重构它们而不必担心影响其他地方的元素或组件。

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

避免死代码

Tailwind 和预编译为原子 CSS CSS in JS 库解决了充满重复规则的膨胀 CSS 文件的问题。

使用原子 CSSCSS 的增长与使用的唯一样式数量相关,而不是开发人员发布的功能数量。

例如,通常在各处重用某些属性如 flex。与在样式表中以不同类名重复这些属性相比,我们只需支付一次成本。对于每个属性/值组合来说都是如此。

弥合设计差距

让我们从所有这些原则和架构中休息一下,记住 CSS 最终是关于实现视觉设计的。

CSS对许多开发人员来说感觉困难的一个重要原因是设计本身很难。

正确掌握基础知识大有裨益。对于视觉设计,一些关键要素是对齐、间距、一致性、大小、排版和颜色。

CSS 中,对于任何给定的属性如字体大小、颜色或填充,有多种实现值的方式。

通常我们会以一种临时方式这样做,导致略有不同的字体大小、间距和颜色不一致,这些都会累积成不完善的外观和感觉。

扩展 CSS 的一个关键部分是通过拥有一组定义间距、字体大小、颜色、断点等值的共享原始值来弥合这种设计差距。

这些通常被称为设计令牌,构成设计系统的基础。没有这个基础,事情可能会感觉非常任意和混乱。

Tailwind 受欢迎的一个关键方面是提供了一套可以即用的预先设计的基础设计原语。这消除了通常临时决策带来的大量决策,从而导致不一致性。

另一个很好的开源选择是 Open Props,与您采用的任何 CSS 方法兼容,并提供大量预先设计的变量和令牌。

结论

“吸收有用的,丢弃无用的,并加入你自己的独特之处”——李小龙

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

专注于组合和构建的原始值也有很大帮助。这也适用于使用组件库的大型组件化应用程序。提供可组合的组件布局原语(如 BoxStackInline 等)是管理 CSS 的好方法,而无需开发人员编写任何 CSS

最近发布的许多功能令人印象深刻,这些功能针对许多使 CSS 难以扩展的痛点进行了改进。诸如级联层、容器查询、子网格、has 等新功能可能会改变我们未来思考和利用 CSS 的方式。

扩展 CSS 的成功不在于对特定原则或最佳实践的教条式坚持,而在于根据实际约束定义需求,并以可持续和高效的方式完成工作。

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