乐趣区

关于facebook:Facebook-重构抛弃-Sass-Less-迎接原子化-CSS-时代

f 随着 Facebook 和 Twitter 最近的产品部署,我认为一个新的趋势正在迟缓增长:Atomic CSS-in-JS。

在这篇文章中,咱们将看到什么是 Atomic CSS(原子 CSS),它如何与 Tailwind CSS 这种实用工具优先的款式库分割起来,目前很多大公司在 React 代码仓库中应用它们。

因为我不是这方面的专家,所以我不会去深入探讨它的利弊。我只是心愿能帮忙你理解它的大抵内容。

先抛出一个令人开心的论断,新的 CSS 编写和构建形式让 Facebook 的主页缩小了 80% 的 CSS 体积。

什么是原子 CSS?

你可能据说过各种 CSS 办法,如 BEM, OOCSS…

<button class=”button button–state-danger”>Danger button</button>

当初,人们真的很喜爱 Tailwind CSS[1] 和它的 实用工具优先(utility-first)[2] 的概念。这与 Functional CSS 和 Tachyon[3] 这个库的理念十分靠近。

<button
class=”bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded”

Button
</button>

用海量的实用工具类(utility classes)组成的样式表,让咱们能够在网页里大显神通。

原子 CSS 就像是实用工具优先(utility-first)CSS 的一个极其版本: 所有 CSS 类都有一个惟一的 CSS 规定。原子 CSS 最后是由 Thierry Koblentz (Yahoo!)在 2013 年挑战 CSS 最佳实际 [4] 时应用的。

/ 原子 CSS /
.bw-2x {
border-width: 2px;
}
.bss {
border-style: solid;
}
.sans {
font-style: sans-serif;
}
.p-1x {
padding: 10px;
}
/ 不是原子 CSS 因为这个类蕴含了两个规定 /
.p-1x-sans {
padding: 10px;
font-style: sans-serif;
}

应用实用工具 / 原子 CSS,咱们能够把结构层和表示层联合起来: 当咱们须要扭转按钮色彩时,咱们间接批改 HTML,而不是 CSS!

这种严密耦合在古代 CSS-in-JS 的 React 代码库中也失去了抵赖,但仿佛 是 CSS 世界里最先对传统的关注点拆散有一些异议。

CSS 权重也不是什么问题,因为咱们应用的是最简略的类选择器。

咱们当初通过 html 标签来增加款式,发现了一些乏味的事儿:

咱们减少新性能的时候,样式表的增长减缓了。

咱们能够到处挪动 html 标签,并且能确保款式也同样失效。

咱们能够删除新个性,并且确保款式也同时被删掉了。

能够必定的毛病是,html 有点臃肿。对于服务器渲染的 web 应用程序来说可能是个毛病,然而类名中的高冗余使得 gzip 能够压缩得很好。同时它能够很好地解决之前反复的 css 规定。

一旦你的实用工具 / 原子 CSS 筹备好了,它将不会有太大的变动或增长。能够更无效地缓存它(你能够将它附加到 vendor.css 中,重新部署的时候它也不会生效)。它还具备相当好的可移植性,能够在任意其余应用程序中应用。

实用工具 / 原子 CSS 的限度

实用工具 / 原子 CSS 看起来很乏味,但它们也带来了一些挑战。

人们通常手工编写实用工具 / 原子 CSS,精心制订命名约定。然而很难保障这个约定易于应用、放弃一致性,而且不会随着工夫的推移而变得臃肿。

这个 CSS 能够团队合作开发并放弃一致性吗? 它受巴士因子 [5] 的影响吗?

巴士系数是软件开发中关于软件专案成员之间讯息与能力集中、未被共享的掂量指标,也有些人称作“货车因子”、“卡车因子”(lottery factor/truck factor)。一个专案或打算至多失去若干要害成员的参加(“被巴士撞了”,指代职业和生存形式变动、婚育、意外伤亡等任意导致缺席的原因)即导致专案陷入凌乱、瘫痪而无奈存续时,这些成员的数量即为巴士系数。

你还须要事后开发好一个不错的实用工具 / 原子样式表,而后能力开始开发新性能。

如果实用工具 / 原子 CSS 是由他人制作的,你将不得不首先学习类命名约定(即便你晓得 CSS 的所有)。这种约定是有主观性的,很可能你不喜爱它。

有时,你须要一些额定的 CSS,而实用工具 / 原子 CSS 并不提供这些 CSS。没有约定好的办法来提供这些一次性款式。

Tailwind 赶来声援

Tailwind 应用的办法是十分便捷的,并且解决了上述一些问题。

它通过 Utility-First 的理念来解决 CSS 的一些毛病,通过形象出一组类名 -> 原子性能的汇合,来防止你为每个 div 都写一个专有的 class,而后整个网站反复写很多反复的款式。

传统卡片款式写法:

Tailwind 卡片款式写法:

它并不是真的为所有网站提供一些惟一的实用工具 CSS,取而代之的是,它提供了一些专用的命名约定。通过一个配置文件[6],你能够为你的网站生成一套专属的实用工具 CSS。

ssh 注:这里原作者没有深刻介绍,为什么说是一套命名约定呢而不是生成一些定死的 CSS 呢?

举几个例子让大家感触一些,Tailwind 提供了一套弱小的构建零碎,比方默认状况下站长博客它提供了一些响应式的断点值:

// tailwind.config.js
module.exports = {
theme: {
screens: {
‘sm’: ‘640px’,
// => @media (min-width: 640px) {…}
‘md’: ‘768px’,
// => @media (min-width: 768px) {…}
‘lg’: ‘1024px’,
// => @media (min-width: 1024px) {…}
‘xl’: ‘1280px’,
// => @media (min-width: 1280px) {…}
}
}
}

你能够随时在配置文件中更改这些断点,比方你所须要的小屏幕 sm 可能指的是更小的 320px,那么你想要在小屏幕时候采纳 flex 布局,还是照常写 sm:flex,遵循同样的约定,只是这个 sm 曾经被你批改成适宜于我的项目需要的值了。

在比如说,Tailwind 里的 spacing 主持了 margin、padding、width 等各个属性里的代表空间占用的值,默认是采纳了 rem 单位,当你在配置里这样覆写后:

// tailwind.config.js
module.exports = {
theme: {
spacing: {
‘1’: ‘8px’,
‘2’: ’12px’,
‘3’: ’16px’,
‘4’: ’24px’,
‘5’: ’32px’,
‘6’: ’48px’,
}
}
}

你再去写 h-6(height), m-2(margin), mb-4(margin-bottom),前面数字的意义就被你扭转了。

兴许从桌面端换到挪动端我的项目,这个 6 代表的含意变成了 6rem,然而这套约定却深深的印在你的脑海里,成为你常识的一部分了。

Tailwind 的常识能够迁徙到其余应用程序,即便它们应用的类名并不完全相同。这让我想起了 React 的「一次学习,到处编写」理念。

我看到一些用户反馈说,Tailwind 提供的类名能笼罩他们 90% – 95% 的需要。这个覆盖面仿佛曾经足够广了,并不需要常常写一次性的 CSS 了。

此时,你可能想晓得为什么要应用原子 CSS 而不是 Tailwind CSS? 强制执行原子 CSS 规定的一个规定,一个类名,有什么益处? 你最终会失去更大的 html 标签和更烦人的命名约定吗?Tailwind 曾经有了足够多的原子类了啊。

那么,咱们是否应该放弃原子 CSS 的想法,而仅仅应用 Tailwind CSS?

Tailwind 是一个优良的解决方案,但依然有一些问题没有解决:

须要学习一套主观的命名约定

CSS 规定插入程序依然很重要

未应用的规定能够轻松删除吗?

咱们如何解决剩下的一次性款式?

与 Tailwind 相比,手写原子 CSS 可能不是最不便的。

和 CSS-in-JS 比拟

CSS-in-JS 和实用工具 / 原子 CSS 有密切关系。这两种办法都提倡应用标签进行款式化。以某种形式试图模拟内联款式,这让它们有了很多类似的个性(比方在挪动某些性能的时候更有信念)。

Christopher Chedeau[7] 始终致力于推广 React 生态系统中 CSS-in-JS 理念。在很屡次演讲中,他都解释了 CSS 的问题:

全局命名空间

依赖

无用代码打消

代码压缩

共享常量

非确定性(Non-Deterministic)解析

隔离

实用工具 / 原子 CSS 也解决了其中的一些问题,但也的确没法解决所有问题(特地是款式的非确定性解析)。

如果它们有很多相似之处,那咱们是否同时应用它们呢?

摸索原子 CSS-in-JS

原子 CSS-in-JS 能够被视为是“自动化的原子 CSS”:

你不再须要创立一个 class 类名约定

通用款式和一次性款式的解决形式是一样的

可能提取页面所须要的的要害 CSS,并进行代码拆分

有机会修复 JS 中 CSS 规定插入程序的问题

我想强调两个特定的解决方案,它们最近推动了两个大规模的原子 CSS-in-JS 的部署应用,来源于以下两个演讲。

React-Native-Web at Twitter (更多细节在 Nicolas Gallagher 的演讲[8])。

Stylex at Facebook (更多细节在 Frank Yan 的演讲[9])。

也能够看看这些库:

Styletron

Fela

Style-Sheet

cxs

otion

css-zero

ui-box

style9

stitches

catom

React-Native-Web

React-Native-Web 是一个十分乏味的库,让浏览器也能够渲染 React-Native 原语。不过咱们这里并不探讨跨平台开发(演讲里有更多细节)。

作为 web 开发人员,你只须要了解 React-Native-Web 是一个惯例的 CSS-in-JS 库,它自带一些原始的 React 组件。所有你写 View 组件的中央,都能够用 div 替换。

React-Native-Web 的作者是 Nicolas Gallagher,他致力于开发 Twitter 挪动版。他们逐步把它部署到挪动设施上,不太确定具体工夫,大略在 2017/2018 年左右。

从那以后,很多公司都在用它(美国职业足球大联盟、Flipkart、Uber、纽约时报……),但最重要的一次部署,则是由 Paul Armstrong 领导的团队在 2019 年推出的新的 Twitter 桌面利用。

Stylex

Stylex 是一个新的 CSS-in-JS 库,Facebook 团队为了 2020 年的 Facebook 利用重构而开发它。将来可能会开源,有可能用另一个名字。

值得一提的是,React-Native-Web 的作者 Nicolas Gallagher 被 Facebook 招安。所以外面呈现一些相熟的概念一点也不奇怪。

我的所有信息都来自演讲 :),还须要期待更多的细节。

可扩展性

不出所料,在 Atomic CSS 的加成下,Twitter 和 Facebook 的 CSS 体积都大幅缩小了,当初它的增长遵循的是对数曲线。不过,简略的利用则会多了一些 初始体积。

Facebook 分享了具体数字:

旧的网站 仅仅首页就用了 413Kb 的 CSS

新的网站 整个站点只用了 74Kb,包含暗黑模式

源码和输入

这两个库的 API 看起来很类似,但也很难说,因为咱们对 Stylex 理解不多。

值得强调的是,React-Native-Web 会扩大 CSS 语法糖,比方 margin: 0,会被输入为 4 个方向的 margin 原子规定。

以一个组件为例,来看看旧版传统 CSS 和新版原子 CSS 输入的区别。

<Component1 classNames=”class1″ /> <Component2 classNames=”class2″ />

.class1 {
background-color: mediumseagreen;
cursor: default;
margin-left: 0px;
}
.class2 {
background-color: thistle;
cursor: default;
jusify-content: flex-start;
margin-left: 0px;
}

能够看出这两个款式中 cursor 和 margin-left 是截然不同的,但它在输入中都会占体积。

再来看看原子 CSS 的输入:

<Component1 classNames=”classA classC classD” />
<Component2 classNames=”classA classB classD classE” />

class a {
cursor: default;
}
class b {
background-color: mediumseagreen;
}
class C {
background-color: thistle;
}
class D {
margin-left: 0px;
}
class E {
jusify-content: flex-start;
}

能够看出,尽管标签上的类名变多了,然而 CSS 的输入体积会随着性能的增多而减缓增长,因为呈现过一次的 CSS Rule 就不会再反复呈现了。

生产环境验证

咱们看看 Twitter 上的标签是什么样子的:

当初,让咱们来看看新 Facebook:

很多人可能会被吓到,然而其实它很好用,而且放弃了 可拜访性[10]。

在 Chrome 里查看款式可能有点难,但 devtools 里就看得很分明了:

CSS 规定程序

与手写的工具 / 原子 CSS 不同,JS 库能让款式不依赖于 CSS 规定的插入程序。

在规定抵触的状况下,失效的不是标签上 class attribute 中的最初一个类,而是样式表中最初插入的规定。

以这张图为例,咱们冀望的是书写在后的 blue 类笼罩后面的类,但实际上 CSS 会以样式表中的程序来决定优先级,最初咱们看到的是红色的文字。

在理论场景中,这些库防止在同一个元素上写入多个规定抵触的类。它们会确保标签上书写在最初的类名失效。其余的被笼罩的类名则被法则掉,甚至压根不会呈现在 DOM 上。

const styles = pseudoLib.create({
red: {color: “red”},
blue: {color: “blue”},
});
// 只会输入 blue 相干的 CSS
<div style={[styles.red, styles.blue]}>
Always blue!
</div>
// 只会输入 red 相干的 CSS
<div style={[styles.blue, styles.red]}>
Always red!
</div>

留神: 只有应用最严格的原子 CSS 库能力实现这种可预测的行为。

如果一个类里有多个 CSS 规定,并且只有其中的一个 CSS 规定被笼罩,那么 CSS-in-JS 库没方法进行相干的过滤,这也是原子 CSS 的劣势之一。

如果一个类只有一个简略的 CSS 规定,如 margin: 0,而笼罩的是 marginTop: 10。像 margin: 0 这样的简写语法被扩大为 4 个不同的原子类,这个库就能更加轻松的过滤掉不该呈现在 DOM 上的类名。

依然喜爱 Tailwind?

只有你相熟所有的 Tailwind 命名约定,你就能够很高效的实现 UI 编写。一旦你相熟了这个设定,就很难回到手写每个 CSS 规定的时代了,就像你写 CSS-in-JS 那样。

没什么能阻止你在原子 CSS-in-JS 的框架上建设你本人的形象 CSS 规定,Styled-system[11] 就能在 CSS-in-JS 库里实现一些相似的事件。它基于一些约定发明出一些原子规定,在 emotion 中应用它试试:

import styled from ‘@emotion/styled’;
import {typography, space, color} from ‘styled-system’;
const Box = styled(‘div’)(typography, space, color);

等效于:

<Box
fontSize={4}
fontWeight=”bold”
p={3}
mb={[4, 5]}
color=”white”
bg=”primary”

Hello
</Box>

甚至有可能在 JS 里复用一些 Tailwind 的命名约定,如果你喜爱的话。

先看些 Tailwind 的代码:

<div className=”absolute inset-0 p-4 bg-blue-500″ />

咱们在谷歌上轻易找一个计划,比方我刚刚发现 react-native-web-tailwindcss[12]:

import {t} from ‘react-native-tailwindcss’;
<View style={[t.absolute, t.inset0, t.p4, t.bgBlue500]} />;

就生产力的角度而言,并没有太大的不同。甚至能够用 TS 来防止错别字。

论断

这就是我要说的对于原子 CSS-in-JS 所有内容。

我素来没有在任何大型生产部署中应用过原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能在某些方面是错的,请随时纠正我。

我感觉在 React 生态系统中,原子 CSS-in-JS 是一个十分值得关注的趋势,我心愿你能从这篇文章中学到一些有用的货色。

退出移动版