StyleX 是什么? 解决了什么问题? 适用在什么场景?

2023年12月18日

💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源
image

最近 Meta 开源了内部使用的 CSS 解决方案 StyleX,不意外地引来社群的一阵讨论。事实上,StyleX 并不是什么新东西,早在 React Conf 2019 时的《Building the New Facebook with React and Relay》讲座中,Frank Yan 就已经提到了,只是当时这个工具还没被开源。在这一期双周报,我们从 Frank 当时的讲座出发,来跟大家聊聊 StyleX。

在这之前,先请大家带入两个情境,首先,假如今天你要去面试前端工程师职位,当被问到“StyleX 解决了什么问题? 适用在什么场景?”你该如何回答? 假如你还没办法解释、分析与比较,以及选择是否用 StyleX,那就让我们一起往下看吧。

首先,StyleX 是个样式系统,它做的事情是透过编译的方式,把样式转成原子 CSS (atomic CSS)。原子 CSS 解决了许多问题,而对于代码可维护性上带来一些好处。

其中一大好处是《浅谈 Atomic CSS 的发展背景与 Tailwind CSS》 提到的“在改样式的时候,你可以直接把 class name 删掉而不用怕影响到其他的元素,这是多么美好的一件事情,你再也不用担心改 A 坏 B,因为 class name 都跟 context 无关了”,这是 StyleX 文件中提的 colocation 能带来的好处。

读到这你可能会问说,写纯 HTML 或用 Svelte 这类框架,把 <style> 跟 HTML 放在同个档案,又或者用 CSS Modules 这类工具,也可以确保 scope 被限缩,不用担心改 A 影响到 B。但这仍会存在一个问题,就是不容易规模化。

《浅谈 Atomic CSS 的发展背景与 Tailwind CSS》有提到原子 CSS 的另一个核心是“Define once, use everywhere”意即定义一次后全局都可以使用。这里说的定义一次,代表着不改 stylesheet 又能改样式。

这也是为什么 Svelte 核心团队的成员在这则推文提到“Imagine if svelte's style tag could be turned into atomic styling, sort of what Vanilla Extract and StyleX do!! ....... Like, instead of the same css coming out, compiler inserts atomic css classes into your markup, and extracts the most common styles out into atomic classes....... CSS will grow at O(logn) after this change”

原本 Svelte 的写法看起来虽然有 colocation,但是 O(n) 的写法,如果你要有新的样式,就要在多定义一个新的。“原子化”的核心就是要做到 colocation 同时又能够把“写 CSS 这件事”从 O(n) 变成 O(logn)

不过就像很多演算法,在测试案例小时,O(n) 解法可能还比较快。在这在写 CSS 也是,小的专案用熟悉的方法,可能还比较快;但大型有超多人要一起维护,就是完全不同的状况了。

这边先稍微总结一下第一点好处,StyleX 让你可以不用担心改 A 结果弄坏 B,与此同是让你可以定义一次就全局使用,修改样式时不会动到样式表,这些都对于写样式有很直接的帮助。

上面谈了 colocation,接着来聊 StyleX 文件谈到的另一个好处 deterministic resolution。谈这点前要先了解在大型专案下写传统的香草 CSS 会有什么问题。在 Frank Yan 的讲座中用很具体的范例来讲解。我们先来看这个简单的 CSS 问题,大家觉得下面的 <span> 会是什么颜色呢?

// css 文件中
.blue {color: blue;}
.red {color: red;}

// HTML 文件中
<span class="red blue">这会是什么颜色?</span>

大家脑中有答案了吗? 如果你觉得是蓝色,可能是因为在 class 当中 blue 在后面,但是这边会是红色,因为在 CSS 的优先序上,文件后者会优先。所以如果你只看 HTML 是没办法判断出会是哪个颜色。

这还不是最让人困扰的,我们来看第二个例子。现在假如 blue 与 red 是在两个不同的档案中,下面的 <span> 会是什么颜色呢?

// blue.css
.blue {color: blue;}

// red.css
.red {color: red;}

// HTML 文件中
<span class="red blue">这会是什么颜色?</span>

这个案例作基本上没办法直接回答出来的,因为要解答这问题,需要去了解这两个 CSS 档案是基于什么打包规则才能回答。对于开发者来说,没办法在写的时候判断,是个很大的问题,而这也正是 StyleX 文件当中提到的 Deterministic resolution 要解决的问题

StyleX 本身的编译器会帮你处理这问题,让你确保在下方这个 <span> 一定会是后者的优先于前者,所以你可以很确定它会是蓝色

<span {...styles.props(styles.red, styles.blue)}></span>

对比起来,同样是原子 CSS 的 Tailwind,现在本身也还没解决这问题,所以在下方的这个范例中,也仍存在不确定最终会是哪个颜色的问题。当然,目前社群中有 Tailwind Merge 这种好用的套件帮忙解决这件事。但这意味需要额外装一个套件,有额外的维护成本。

// Tailwind 范例
<span class="text-blue-100 text-red-100">这会是什么颜色?</span>

当然你可能会说,优先级在香草 CSS 的脉络下,也不是没办法被处理。只是大型专案下,东西变多变复杂,若要解决优先度的问题,往往把类别定得很详细,但这就会导致 Frank Yan 提的“优先级战争”,当专案一大一复杂、经历多次重构,就会出现下面这种超长的类别,也就是 StyleX 文件中提到的 bloated CSS 问题,这会导致代码很难被维护。

.coolest-redesign .cooler-redesign .cool-redesign .cool {
  // ...
}

以笔者自己的经验来说,之前有处理过一个在微前端架构,主应用加东西,多数子应用都正常渲染,唯独有几个子应用,在该新功能的样式盖过主应用的样式。这是《浅谈 CSS 方法论与 Atomic CSS》有谈到 “一但开发的专案庞大起来,并且有多位前端工程师在进行代码撰写时,就很容易遇到命名冲突、stylesheet 过于庞大等问题,主因是在 CSS 的世界中,所有规则集都是全域的”

你可能会说,命名前跟别人沟通协调一下。当然可以这样做,但这样做就是额外的时间成本,而在大的专案下这么做,就会耗费更多时间成本,这显然是不乐见的。以笔者上面提到的问题来说,不同子应用是不同团队维护,团队在不同国家,沟通存在时差问题,来回可能就是一两天过去。

因此不是说其他方法做不到,而是 StyleX 的作法,是用更简单、更容易维护复杂专案的方式,来实现 deterministic resolution,让大型、跨国跨时区的专案,可以省去这些成本。

除了上面提到的 colocation 与 deterministic resolution,StyleX 还有许多其他好处,例如型别检查,这是 Tailwind 目前也还没做到的,在开发上能有效避免开发者写错东西。但碍于电子报的篇幅,今天就先不多谈这些其他好处,推荐大家可以到 StyleX 的官方文件了解 [连结]

回到上面提的,如果面试中被问“StyleX 解决了什么问题? 跟其他 CSS 解决方案分别适用在什么场景?”希望这篇内容能让大家讲得出 colocation 与 deterministic resolution 这两点。

至于适用的场景,确实若是在一个小型、没有太多样式要定义、不牵涉多人维护、不会频繁迭代的专案的情境下,写香草 CSS 其实很足够。以 DHH 为例,他到现在开新的专案,还是选香草 CSS,也完全没问题。但如果是大型、各种样式复杂、跨团队跨时区的协作、迭代很频繁的专案,StyleX 能比上很大的忙。

不过你可能会问,比起其他的原子 CSS,例如 Tailwind,StyleX 的额外好处在哪? 整体比较起来,StyleX 多的好处是上面提到的 deterministic resolution 不需用额外装一个 Tailwind Merge 就能做到,以及 Type-Safe 这点,也是在大型专案很有帮助的。

资料来源

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們