qhxin

Virtual CSS 和 Styletron

qhxin · 2016-12-17翻译 · 827阅读 原文链接

Styletron 是一个为了高性能而建立的 CSS-in-JS 的库,旨在运行得尽可能的快,同时尽可能小的输出 CSS。

大部分 CSS-in-JS 库将 JS 对象包含样式声明为相应的 CSS 类并使用生成的散列作为类名1。总的来说,这一过程运行良好,解决了大多数的CSS问题2

Styletron 采取了不同的方法。它使用一个“虚拟CSS”引擎,抽象了底层的 CSS ,并启用一些强大的性能优化。

原子 CSS 和虚拟类

关键的想法是,Styletron 把所有东西分解成唯一的声明,并且为每一个唯一属性值对创建一个对应的“原子CSS”类3,而不是生成简单地1:1映射到源样式对象的CSS类。结果就是,Styletron 可以对应的生成由几个原子类组成的虚拟类,而不是生成整块的样式。

这种抽象获得了几个性能优势:

非增长的样式

最终在给定的应用里只有有限数量的不同字体大小,宽度和颜色。并且在实践中,为设计一致性的目的,这些选项经常被有意地限制在一组共用的值中,然后在应用中多次重复这些共用值。

Styletron 可以利用这点,因为它的虚拟 CSS 抽象在声明层面就去除了重复的样式,即使是不同的规则。在某种意义上,CSS 变成了非增长的:在某种程度上,新添加的样式实际上只是预先存在的声明的新排列,它映射到已经存在的原子类,生成CSS是零附加字节的。换句话说,虚拟 CSS 的 CSS 输出规模的大小由唯一样式声明的数量决定,而不是使用的样式的数量或频率。

在实践中,这会对尺寸产生巨大的影响,特别是在声明的总数远远超过唯一声明数目的大型网站。

最小的可能的样式

CSS 优化的很重要的一点是把关键的和非关键的 CSS 分开,并且除掉没有使用的 CSS,这些没有使用的 CSS 白白浪费了浏览器的下载和解析。和许多其他的 CSS-in-JS 解决方案一样,Styletron 自动地在它的输出里面消除了任何没有使用的以及非关键的 CSS,只留下关键的CSS 给任何给定的服务器渲染的页面。

这通常是和它得到的一样好的,但是,即使是在消除了非关键的和未使用的 CSS之后,Styletron 还更进一步地减少了关键样式表的大小。虽然 gzip 通常是一个好的压缩样式表的方法,但在 Styletron里,所有的重复的声明都在 gzip 压缩之前被清理了。结果就是,浏览器可以更少地下载和解析,后者是不受 gzip 影响的。

此外,因为由 Styletron 生成的样式表是如此的小,他们能够很容易的内联到页面里去。这和页面的性能有关:确保移动设备上的快速加载时间,AMP 规范实际上禁止外部 CSS 资源,强制所有的 CSS 内联(有一个 50KB 的最大尺寸限制)。

很快的运行速度

Styletron 是为高性能设计的,不仅在小的 CSS 输出方面,还有执行时间。和许多其他生产有限范围散列的 CSS 的 CSS-in-JS 库不一样,Styletron 不需要执行性能昂贵的哈希计算;相反,它生成的类由共享的、复用的、有序列化标识符的原子类组成4。此外,因为它是在个体的声明而不是整体规则上操作,Styletron 可以减少缓存未命中并且利用非常精细的记忆以避免不必要的工作。

程序基准测试

css-in-js-perf-tests 库有许多针对大多数受欢迎的 CSS-in-JS 库的基准测试,包括 Aphrodite,Glamor,JSS,还有 Syletron。下面显示的数字是在我的 MacBook Pro 上用 Node7 运行基准测试的结果。

注:下面的 JSS 的基准测试表明没有使用默认预设,所以没有包含额外的如嵌套规则或第三方前缀。

渲染性能

简单的样式测试

这个测试渲染少量的基础样式,一个是容器,一个是按钮。

样式过载测试

这个测试给n个不一样的按钮元素生成n个独有的样式,默认情况下n=20

类过载测试

这个测试给n个不一样的按钮元素生成n个完全一样的样式,同样地,默认情况下n=20

不像其他的库,Styletron 分解了规则,因此能够记住相同的声明,因此,在这个测试中的性能优势尤为明显。

CSS输出结果的大小

作为一个大的 CSS-in-JS 应用的模拟,我使用uber.comairbnb.com的CSS,转换这些样式为 JS 的等价对象,然后在 Styletron、JSS、Glamor 和 Aphrodite 里处理它们。以下是 CSS 输出文件的大小结果:

uber.com CSS - 原文件

airbnb.com CSS - 原文件

一般来说,复用的样式越多,随着越来越多的 CSS 被去重,Styletron (和其他库比起来)生成的CSS越小。

JS 包大小(最小化的)

Styletron 还是这些库里面最小的。它尽可能的被设计得小,而且客户端和服务端的包是分离的,这有助于减少臃肿。

用法

实际的核心 Styletron 模块是非常低级的,这让它对于许多不同的 CSS-in-JS 接口是高适应性的,但直接使用是很笨重的。

最简单的方法是 Styletron 和 styletron-utils 包一起使用,styletron-utils 包包含各种方便功能,包括获取样式对象并返回相应虚拟类名的injectStyle。这个接口大致和其他 CSS-in-JS 库提供的接口一样。

最后,在最高水平,有一个 styletron-react 包,它有一个接口是受 React 优秀的 styled-components 模块启发的灵感。

参考 GitHub repostyletron.js.org 可以了解更多。

已知问题和权衡

后代选择器和子选择器是不受支持的

虽然在使用 React(样式与模板和渲染逻辑在一个地方)的时候这些东西不是经常地使用,它们在某些情况下仍然有用5。就是说,由于技术原因,这些可能不会在 Styletron 得到支持,至少在短期内。幸运的是,任何在 CSS 里由后代选择器和子选择器可以实现的效果都可以由渲染逻辑(JavaScript)实现。

至今没有降级处理

这是正在制定的问题,但目前没有实施。

本地工具的工作流的影响

使用虚拟类的一个结果是,当检查元素时,将在样式面板中看到每个声明的类,有时是很麻烦的。此外,在 Chrome 的本地开发工具里,客户端渲染的原子类是不变的——他们在元素上可以被打开和关闭,但是由于 Chromium 的 bug6 而不会改变。服务端渲染的样式是不受影响的,这个 bug 只影响 Chrome(Safari 和 Firefox 上表现得很好)

脚注


  1. 多数 CSS-in-JS 库,包括 Aphrodite、Glamor、JSS,等等。使用下面的方法:

类名称生成作用域的方法虽然不同,但这都是一个在生成的CSS类声明的集合之间 1:1 的映射。

  1. Christopher Chedeau 的 开创性的介绍 CSS-in-JS 提供了一个良好的CSS缺陷概述以及在 JavaScript 中的样式怎么成为一个解决方案。

  1. 这在概念上类似于 react-native-web 样式表模块(这实际上开创了为服务端渲染网页生成原子 CSS 的思想),但是 Styletron 以几个关键的方式使它更进一步:

  2. Styletron 为所有样式工作——静态和动态的都可以。react-native-web 样式表仅限于可以在渲染之外解决的静态样式。

  3. react-native-web 样式表仅在服务端工作;在客户端,使用普通的 React 内联样式。

  4. react-native-web 缺少媒体查询和伪类的支持, Styletron 支持这两者。

  1. 使用确定性散列的一个原因是服务器和客户机渲染之间的一致性。因为 Styletron 的 CSS 输出仅由单一声明的原子类组成,它本质上是一个来自服务器的声明缓存的全序列化。因此,服务端渲染的 CSS 可以有效地反序列化,完全混合在客户端缓存的状态。

  1. 后代选择器和伪类相结合是很有用的,最常见的用例是在父节点的悬停状态触发子节点的样式更改。然而,如果需要的话,这种行为可以在 JavaScript 中以事件监听和显式状态变化的方式得以实现。

  1. 不幸的是,这个 bug 目前是被标记为不会修复

相关文章