[译]真实React项目中CSS与CSS-in-JS的性能比较

3,296 阅读8分钟

在真实应用中(而不是简单的todo类型应用)单独加载的 CSS 文件与CSS-in-JS文件,性能相差多少?

前言

CSS-in-JS在前端工具库中占据了一席之地,而且似乎在不远的将来它的势头仍在。React世界里尤为突出,例如在 State of CSS对11492人的调查中,只有14.3%的人从未听说过Styled Components (一个在CSS-in-JS中占据主导地位的库),而超过40%的人曾经使用过这个库。

很长一段时间以来,我都非常想要看到像Styled Components这样的CSS-in-JS库和一个优秀的旧式CSS库的深入性能比较。但遗憾的是,除了一些简单的测试场景,我没能在真实的实际项目中找到相关比较。

所以,我决定自己做。我已经将一个真实的应用项目从Styled Components迁移到了Linaria(译者注:一个zero-runtime的CSS-in-JS库,构建过程中会将css提取到css文件,也就是作者想要的用旧式CSS构建网页)中,它将在构建时提取CSS。当在用户设备上运行时,它不会生成样式。

在我们开始之前,我先声明,我并不讨厌CSS-in-JS。我承认它们拥有很棒的开发者体验(DX,即Developer Experience),而且优秀地继承了React的组合模型。它可以为开发人员提供一些不错的优势,例如Josh W. Comeau在他的文章The styled-components Happy Path中提到的亮点。我也在自己的一些项目和其他我参与过的项目中使用了Styled Components。但是我想知道,站在用户的角度来看,这优秀的开发者体验背后,它的代价是什么。

让我们来看看我发现了什么。

简要总结

如果您关心站点的加载性能,请不要使用运行时的CSS-in-JS。更少的JS=更快的网站。对此,我们基本无能为力。如果你想要看到一些详细数值,请继续往下阅读。

测试方式

我使用的测试应用程序是一个相当标准的React app,使用了Create React App脚手架,搭配Redux,并且使用Styled components(v5)样式库。这是一个相当大的应用程序,拥有许多页面,还有可以定制的仪表盘、客户主题等。因为它是用Create React App搭建的,它没有用服务端渲染,所有内容都会在客户端进行渲染(因为它是B2B app,这并不是必需的)。

我使用了这个应用程序,并用Linaria替换了Styled Components,它似乎拥有一个类似的API。我以为转换会很容易,但事实证明并不是这样,迁移这个项目花了我差不多两个多月,最终也只迁移了几个页面,而不是整个应用程序。我想这就是为什么我之前没找到这样的比较 😅。变更样式库是唯一的更改,其他一切都未做变动。

我使用Chrome dev tools在两个最常用的页面上运行了几个测试。每个测试都进行了三次,所显示的数字是三次运行的平均值。对于所有测试,我将CPU throttling设置为4x,并将network throttling设置为Slow 3G,所使用的是单独Chrome标准配置文件,没有其他任何扩展。

进行以下几项测试:

  1. 网络(JS和CSS静态资源的大小、覆盖率、请求数)
  2. Lighthouse(使用移动设备进行性能类别测试)
  3. 性能(页面加载测试、拖放交互测试)

网络比较

我们首先进行网络比较。CSS-in-JS的优点之一就是没有使用样式,但实际上不完全是这样。

虽然你只激活了页面上才使用的样式,但实际上可能仍然会下载不必要的样式。但它们不会放到单独的CSS文件中,而是被放到了JS包里。

这是使用Styled Components和Linaria构建相同主页的数据比较。斜线前是压缩后的大小,斜线后是未压缩的大小。

  • 首页网络统计比较
Styled ComponentLinaria
Total number of requests1113
Total size361kB/1.8MB356kB/1.8MB
CSS size2.3kB/7.2kB14.7kB/71.5kB
No. of CSS requests13
JS size322kB/1.8MB305kB/1.7MB
No. of JS requests66
  • 搜索页网络统计比较
Styled ComponentLinaria
Total number of requests1012
Total size395kB/1.9MB391kB/1.9MB
CSS size2.3kB/7.2kB16.0kB/70.0kB
No. of CSS requests13
JS size363kB/1.9MB345kB/1.8MB
No. of JS requests66

尽管我们的CSS负载增加了很多,但我们在两个测试用例下的下载数据总量仍然较少(这种情况下差异几乎可以忽略不计)。但更重要的是,Linaria的CSS和JS总和仍然小于Styled Components中JS本身的大小。

覆盖率

如果我们比较覆盖率,我们会发现Linaria有很多未使用的CSS(大约55KB),而Styled Component有6KB(这些CSS来自npm包,而不是来自Styled Components本身)。与Styled Component相比,Linaria未使用的JS数据要小20KB。但是Linaria中未使用的静态资源总体规模更大。这是使用外部CSS的权衡考量之一。

  • 覆盖率比较 - 首页
Styled ComponentLinaria
Size of unused CSS6.5kB55.6kB
Size of unused JS932kB915kB
Total size938.5kB970.6kB
  • 覆盖率比较 - 搜索页
Styled ComponentLinaria
Size of unused CSS6.3kB52.9kB
Size of unused JS937kB912kB
Total size938.5kB970.6kB

Lighthouse性能比较

如果我们谈论性能,那一定得拿出Lighthouse。你可以在下面的表格中看到比较结果(取三次LI运行的平均值)。除了Web Vitals(译者注:性能关键点,用于衡量页面质量),我还测试了包括主线程工作(解析、变异、执行静态资源的时间,其中最长的时间部分是JS名单它涵盖了布局、样式计算、渲染绘制等)和JS执行时间。我省略了Cumulative Layout Shift(累积布局偏移)因为它基本为零,而且在Linaria和Styled Component之间几乎没有区别。

image.png

image.png

如图所示,Linaria在大多数Web Vitals中表现更好(在CLS中输了一回),有时甚至具有较大优势。例如,LCP(Largest Contentful Paint,最大内容绘制)在主页上快了1.2秒。使用普通CSS渲染页面不仅速度更快,而且需要的资源也更少。阻塞时间和执行所有JS所需的时间分别减少了300毫秒和大约1.3秒。

性能分析

Lighthouse可以提供许多有关性能的见解。但想要深入了解细节,开发工具中的性能选项卡是最好的选择。在这种情况下,性能选项卡将确认Lighthouse的结果。详细信息在下面的图表中。

image.png

image.png

使用Styled Component组件构建的屏幕具有更多长时间的运行任务。与Linaria相比,这些任务也需要更长的时间才能完成。

为了让你再次查看数据,这里是使用Styled Component(顶部)和Linaria(底部)加载主页的性能图表的可视化比较图。

image.png

用户交互比较

为了更好地比较用户交互,而不仅仅是页面加载。我测量了进行项目分组的拖放活动性能。结果总结如下:即使在这种情况下,Linaria在几个类别中也击败了CSS-in-JS。

  • 拖放活动比较
Styled ComponentLinariaDiff
Scripting29552392-563ms
Rendering30022525-477ms
Painting329313-16ms
Total Blocking Time1862.66994.07-868ms

image.png

总结

正如你所见,运行时的CSS-in-JS会对网页产生显著影响。以上主要适用于互联网连接速度较慢或数据费用较高的低端设备和地区。

所以也许我们应该更好地思考我们使用工具的内容和方式。优秀的开发人员体验不应以牺牲用户体验为代价。

我相信,我们开发人员会更多地考虑我们为项目选择的工具所带来的影响。下次当我想要开启一个新的项目时,我将不再使用运行时的CSS-in-JS。我将会选择使用好的旧式纯CSS库,或选择一些构建时的CSS-in-JS作为替代品,来从JS包中获取样式。

我认为随着越来越多库的出现(最后一个是Seek的vanilla-extract)构建时的CSS-in-JS将会成为CSS生态系统中的下一件大事,许多大公司也在朝这个方向发展,比如Facebook的styling lib

感谢您阅读这篇文章。如果您对如何改进文章有任何建议和想法,或者您只是单纯喜欢它,都随时欢迎在Twitter上进行分享和讨论。

附录:CSS-in-JS相关文档