让Cloud.typography变得更快(呃)。

235 阅读15分钟

免责声明。

  • 我没有被Hoefler&Co或Cloud.typography接触或雇用调查以下任何问题。
  • 我向Cloud.typography披露了以下所有情况,并给了他们充分的机会来共同解决问题的根源。 他们没有这样做的欲望,所以我决定把这些都免费提供给大家,因为更快的网络对每个人都有好处。
  • 所有与我打交道的人看起来都是非常非常好的人。 下面的内容不是对任何个人的反映。请你在阅读时记住这一点。

这一切的开始,就像这些事情经常发生的那样,是由一个瀑布开始的。

仅仅几个条目就有这么多东西需要解读!你能看到吗?你能看到它吗?查看全尺寸 (31KB)

我正在做一些粗略的研究,并针对一个潜在客户的网站进行一些测试,以便在我们合作之前对事物的形态有一个良好的了解。即使在上面的瀑布图的前10个条目中,也有许多迷人的线索和告诉被立即显现出来。在页面加载生命周期的早期阶段,我被一些有趣的行为所震惊。让我们把它拆开来看看...

  1. 条目(9),对于cloud.typography.com ,有非常高的连接开销(总共405ms),令人惊讶的大TTFB(210ms),并且无论如何都会返回一个302。这里发生了什么?我们被重定向到哪里了?
    • 事实证明,Cloud.typography正在将请求重定向到一个托管在fonts.[client].com 上的客户拥有的CSS文件:注意一个Location: https://fonts.[client].com/[number]/[hash].css 头。
  2. 条目(10)又住在一个不同的源头,所以我们有更多的连接开销要处理,而且文件似乎要花很长时间来下载(从大量的深绿色发送数据可以看出)。 为什么Cloud.typography把我们重定向到一个我们自己拥有的文件?是否有某种 "发布机制 "用于验证[client].com ,以使用字体服务?还有,为什么这个文件这么大呢?
    • 事实证明,Cloud.typography是一个混合解决方案,介于云端和自我托管之间。从白名单上的域名发出的请求会返回一个302 ,将请求转发到一个自我托管的CSS文件,该文件专门针对你的浏览器、操作系统和UA进行了优化(谷歌字体也做了类似的事情)。我们托管的CSS文件是由Cloud.typography提供的。
      • 如果你想知道,一个非白名单的域名会返回一个403响应。
    • 这个文件之所以这么大,是因为它实际上包含了我们所有的字体,作为Base64编码的数据URI。
  3. 最后,条目(3),一个大的JS文件,直到CSS(条目(10))完成后才执行。由此,我可以得出结论,条目(3)是一个同步的JS文件,是在CSS之后的某个时间在HTML中定义的。
    • 我是怎么知道的?因为在同步CSS之后的任何地方定义的同步JS,在有关的CSS处于飞行状态时不会执行。JS可以在CSSOM构建时被阻止。这种长的CSS请求链的连锁反应是巨大的。

那么,这一切对性能有什么影响呢?

首先,尽管对cloud.typography.com 的请求返回一个text/html MIME类型的302响应,但其发出的请求是用于CSS。这完全说得通,因为这个请求是由<link rel="stylesheet" /> 。我们可以通过注意到Accept: text/css,*/*;q=0.1 请求头的存在来进一步验证这一点。简单地说,尽管它不是CSS,但这个请求位于我们的 "关键路径 "上,因此会阻止渲染。

为了进一步加剧这个问题,302 响应有一个Cache-Control: must-revalidate, private标头,这意味着无论我们是通过冷缓存还是热缓存访问网站,我们都会对这个资源发出请求。虽然这个响应的文件大小为0B,但我们总是会在每一个页面浏览中承受延迟的冲击(这个响应基本上是100%的延迟)。在移动连接上,这可能相当于整整几秒钟的延迟,都在关键路径上。

接下来,我们会被发送到fonts.[client].com ,这为连接设置带来了更多的延迟。请注意,这种现象是这家公司实现自己资产的方式所特有的,与Cloud.typography完全无关)。因为这个响应是CSS,所以我们的关键请求链仍然没有中断--这是所有在关键路径上进行的工作。

一旦我们处理好了连接开销,我们就开始下载一个巨大的CSS文件(271.3KB),其中包含了项目中所有以Base64数据URI编码的字体。Base64对于性能来说绝对是糟糕的,尤其是在字体方面,它有以下问题。

  • Base64编码的资产到CSS中会将更多的重量(因此是延迟)转移到关键路径上,使我们的开始渲染受到阻碍。
  • Base64的压缩率很高,这意味着我们无法收回过多的、增加的文件大小。
  • 字体通常不会被下载,直到Render Tree被构建,并且浏览器知道当前页面确实需要它们。浏览器的这一防御措施确保只有真正需要的字体才会被下载。如果将字体编码到CSS中,那么所有的字体数据都会被下载,而不管当前页面是否需要它:这实在是太浪费了。
  • 通过对字体进行Base64编码,我们最终会发现,从技术上讲,我们根本就没有任何字体:我们只有CSS。这里的含义是,我们现在已经使任何字体加载策略完全失效了:如果没有字体,font-display ;如果没有字体,字体加载API就没有用。从技术上讲,我们在这里遇到的是一个CSS问题,而不是一个字体问题。

上述情况的实际结果是,我们的 "关键路径"(Critical Path)上有1,363ms的渲染阻塞CSS,用于有线连接上的首次访问。重复浏览仍然花费了280ms。

因为Cloud.typography的 "CSS文件 "有一个must-revalidate 头,所以我们在重复浏览时仍然会受到延迟的影响。查看全尺寸(17KB)

棺材上的最后一颗钉子是,由于没有字体文件,浏览器无法采用任何关于如何处理缺失字体的决定。大多数浏览器会在三秒钟内显示不可见的文本,如果网络字体在这段时间内没有到达设备上,就会显示后备文本。这意味着,一旦页面开始渲染,用户最多只能在三秒内无法阅读任何内容。

这一切只有在有实际的字体文件需要协商的情况下才有效。因为Cloud.typography的字体实际上只是更多的CSS,浏览器无法辨别这两者,因此无法提供三秒钟的宽限期。这意味着,在理论上有一个无限期的时间,我们不只是阻止文本的渲染,而是阻止整个页面的渲染。

如果你有兴趣看到这个现实世界的例子,可以考虑下面的电影片段对比

Post-It是Cloud.typography的客户,在Cloud.typography的CSS文件进入设备之前,整个页面都保持空白。另一方面,Grid By Example使用谷歌字体(没有任何font-display 配置),这使得浏览器可以在没有网络字体的情况下开始渲染页面。

**注意:**这两个测试案例是非常非常不同的网站,所以我不希望直接比较任何时间--那将是不公平的--我只是想强调这一现象。

在3G连接下,Post-It的开始渲染时间是16.8秒。如果完全删除Cloud.typography,这个时间会提高48%以上,降到8.7秒。

所有上述问题加在一起意味着,不幸的是,Cloud.typography默认速度很慢。虽然我是这个字库的超级粉丝,也是他们制作的一些令人惊叹的字体的粉丝,但我不能凭良心推荐Cloud.typography作为一个可靠的字体供应商,而这是他们的实现方式。 它对性能是有害的。

为客户解决问题

我为我的客户实施的解决方案充其量只是缓解措施--Cloud.typography的问题仍然存在,但我能够做一些事情来消除这些问题的表现形式。

首先,我想解除对JS执行的封锁:没有必要为了字体的缘故而保留主要的app.js 捆绑。如何解决?很简单:把它们的HTML中的<link rel="stylesheet" /><script> 行交换一下。

之前。

<link rel="stylesheet" href="https://cloud.typography.com/[number]/[number]/css/fonts.css" />
<script src="https://www.[client].com/packs/application-[hash].js"></script>

后。

<script src="https://www.[client].com/packs/application-[hash].js"></script>
<link rel="stylesheet" href="https://cloud.typography.com/[number]/[number]/css/fonts.css" />

是的。就这么简单。现在请注意,第(3)项在fonts.[client].com 文件开始下载之前就已经执行了。

通过在我们的JS之后加载CSS,JS可以更早地运行。查看全尺寸 (29KB)

我的第二个策略是,通过简单地预先连接到该原点,预先支付fonts.[client].com的连接费用。在<head> 的早期。

<link rel="preconnect" href="https://fonts.[client].com" />

注意条目(11),其中我们正在处理关键路径之外的高达397ms的连接开销。

这两个小变化导致了开始渲染的300ms改善,以及在50分位数时TTI惊人的1504ms改善。

**注意:**请记住,这些变化都没有解决Cloud.typography中固有的任何问题。我在这里所做的只是尽我所能在客户端减轻这些问题。

我们来谈谈

需要一些相同的东西吗?

我可以通过研讨会咨询建议开发来帮助你。

为大家解决问题

我们已经写了1300多字了,但我甚至还没有明确解决这个问题的关键。Cloud.typography并没有给我们带来字体性能问题,而是给我们带来了CSS性能问题。因为字体是Base64编码的,所以没有字体。这意味着,无论多少font-display 、字体加载API、Font Face Observer等都无法帮助我们。我们如何从源头上解决这个问题呢?我与Cloud.typography取得了联系。

伸出援手

说实话,我一开始有些迷茫。在 "关键路径"(Critical Path)和Base64编码的字体上有不可缓存的、跨源的重定向,这似乎是一种非常缓慢的资产交付方式。所以,很自然地,我想检查一下我们是否做错了什么。我还想更彻底地了解该解决方案的端到端工作原理,这样我就能更好地提出建议。

我得到了Hoefler&Co团队成员的详细信息,并询问我是否可以向他们发送一些与他们的云服务有关的问题。如果你有兴趣,你可以阅读未经编辑的电子邮件全文

我得到的答复对一些事情有了一些启发。简而言之。

  • 客户的实施是正确的。
  • 重定向并不 "释放 "字体以供使用,但实际上收集了一些关于你的浏览器和操作系统的信息,并将请求转发给Cloud.typography为你提供的许多CSS文件中正确的一个,让你自行托管。
    • 重定向的Location ,这在很大程度上取决于提出请求的用户代理,所以你不能规避Cloud.typography的行程。
    • (作者注:但别搞错了,重定向的存在主要是为了跟踪使用情况,否则他们会把它缓存起来。)
  • 云服务是针对那些可能需要访问一个更大的库来完成许多不同项目的开发者和机构。
  • 自我托管选项针对的是那些拥有有限的、严格定义的字体调色板的公司(考虑更多企业客户)。
  • 我们鼓励任何关心性能的人升级到自我托管的计划。

我非常感谢这个人的时间和耐心,我只是突然出现在他们的雷达上,并有相当多的问题,而且,我承认,对他们有批评。他们的答复很有见地,也很及时。

然而,在这里,我最终有点沮丧:尽管清楚地概述了Cloud.typography对性能的具体影响,但没有兴趣去研究如何补救这些问题。没有人愿意提供甚至记录替代性的(即不是_替代_--目前的方法仍然是完全有效的)非阻塞性加载策略。

我想出了一个概念验证的替代性加载策略,它不会阻塞渲染,而是选择异步加载资产,以换取一个闪光的回退文本(FOFT),类似于实现font-display: swap; ,我被告知客户绝大多数都喜欢不要让他们的页面加载无字体,然后 "弹 "出正确的字体...当然,我、每个异步字体加载策略和整个font-display/字体加载规范都不同意。

我了解到,目前HTTP档案中大约有13100个网站使用Hoefler&Co的Cloud.typography服务。这意味着每年大约有价值1550万美元的客户被捆绑在一个缓慢的设计、缓慢的默认网络字体解决方案上,而我们本可以帮助他们摆脱困境!这真是太遗憾了。

这是一个真正的耻辱,因为解决方案是微不足道的,我已经做了所有的法律工作,但这就是生活。

解决方案

重申一下:我们没有网络字体问题,我们有一个CSS问题。考虑到这一点,这个问题的解决方案就变得非常简单:我们只需要懒惰地加载我们的CSS。通过懒惰加载我们的字体样式表,我们把它从 "关键路径"(Critical Path)上移开,并解除了渲染的障碍。

自从Critical CSS出现以来,懒惰加载样式表已经变得越来越普遍,Filament Group的最新方法是迄今为止最简单和最广泛支持的。

<link rel="stylesheet" href="https://cloud.typography.com/[number]/[number]/css/fonts.css"
      media="print" onload="this.media='all'" />

神奇之处在于第二行。通过设置一个不匹配的媒体类型,浏览器自然而然地将样式表以低优先级从关键路径上加载。一旦文件加载完毕,内嵌的onload 事件处理程序就会切换到一个匹配的媒体类型,然后这个变化就会将样式表应用到文档中,将字体替换进来。

这个方法的一个注意事项是,我们现在确实有一个闪光的回退文本(FOFT)。这种加载样式表的方法有效地合成了font-display: swap; ,立即显示一个回退字体,并在我们选择的网络字体可用时将其替换。这意味着,当我们在等待合适的字体到来时,你必须设计一个非常强大、准确的后备样式供用户体验。 值得庆幸的是,Monica通过给我们提供字体样式匹配器,使之变得简单了许多。

让我特别喜欢这个方法的是,我们已经从可以说是最慢的加载资产的方法变成了可能是最不具干扰性的。这是一种完全颠倒的范式。

下面是通过3G连接的前后对比。左边是最初的实现,右边是我的固定版本。

没有看到任何东西?点击这里!

是的,我们确实有一个FOFT,但我们更早地将文本送到读者面前,这是一个巨大的改进。

结束语

我对Cloud.typography对这些问题的漠不关心感到非常沮丧,尤其是考虑到 "修复 "只需要更新文档就可以了。他们不需要做任何平台或基础设施方面的改变。此外,他们仍然能够保持同样的控制水平,并完全跟踪字体的使用情况,但完全是异步的。这对他们来说也是一个很好的新闻报道,宣布发布一个更快的方法来包括同样漂亮的字体。

也就是说,如果你是Cloud.typography的客户,不要惊慌。我建议你探索我上面概述的异步方法。实施本身应该是微不足道的,但你需要投入一些时间来设计一个合适的回退样式,同时你的CSS正在加载。

撇开具体的网络字体供应商不谈,我认为整个情况是一个迷人的案例研究,说明如果允许足够多的小错误合并,事情就会开始下滑。对我来说,剥开每一层,看看它是如何影响问题的下一个部分的,还真有点意思,我希望我的详细说明在这个过程中能让人们学到一两件事情。

如果要我用一个启示来总结整个文章的话,那就是CSS的性能绝对是至关重要的,而且随着目前忽视和否定CSS作为其自身学科的趋势,像这样的案例变得非常普遍。

CSS很重要。非常重要。