[译]工程师揭秘在Quora缩短页面加载时间用到哪些技术(下)


原文:ENGINEERING AT QUORA Faster Paint Times
译者:杰微刊兼职译者张迪

[译]工程师揭秘在Quora缩短页面加载时间用到哪些技术(上)

 

尽可能把一些渲染blocking字节减少
组块中简单的服务页面并不一定需要保证用户看到内容的时间更早,因为页面的第一次“绘制”也被CSS阻碍了。为了防止没有经过设计的内容的不和谐flash,浏览器不会渲染任何内容,直到页面的CSS已经完全被下载和解析。所以,即使HTML页面(或一个组块)已被下载,用户仍然只能看到一个空白的屏幕,直到CSS也完成下载。浏览器的导航时间设置,资源时间设置和paint时间设置包含每个页面第一次“绘制”结果(多久)的具体组块的更多信息。


解决这个问题的一个常规办法是直接插入一个网页的关键CSS,或者页面的最低CSS规则集。与其迫使浏览器下载整个CSS有效载荷——可能包含不应用于网页的CSS规则,或应用到元素的规则在最初就被隐藏——在渲染任何东西之前,还不如我们在页面上直接嵌入必要的CSS规则,这意味着浏览器不需要阻止外部样式表的渲染。然后,浏览器可以读取样式表,可以为后续请求进行缓存。


对于一个动态网站来说,一个页面的关键CSS可能与每个请求不同。考虑到我们在上面定义的feedstoryitem组件——不仅在HTML子树上进行定义,它也有一组相关的CSS规则。因为这个组件是可以重用的,开发人员应该能够很容易地将其添加到一个新的页面,在这一点上,它的CSS成为网页的部分关键CSS。此外,任何组件都可以在页的条件呈现,所以相关的CSS可能或不可能成为页面的部分关键CSS。


为了动态地确定在一个在Web请求的上下文上的页面关键CSS,我们创建了一个叫ParseCSS的工具,这个工具资源如今已经被我们公开了。然而,许多其他重要的CSS工具是为了大多数静态网站设计的——输入已生成的HTML文件和它的CSS——ParseCSS是专门为动态页面设计的,其中关键的CSS可能会改变每个请求。


Parsecss是使用Node.js和流行的postcsslibrary编写的一个独立的命令行工具。给定一个CSS文件,ParseCSS会输出一个CSS AST的JSON陈述,所以开发者不需要直接处理AST。使用此输出,我们可以确定我们的渲染代码风格应该是直接嵌入在每个响应上。
Parsecss的输出看起来像下面所显示的内容:

{
    "globalCss": [
        "html, body {...}",
        ...
    ],
 
    "keyframesCss": [
        ["fadeIn", "@keyframes fadeIn {...}"],
        ...
    ],
 
    "fontfaceCss": [
        "@fontface {...}",
        ...
    ],
 
    "classListCssPairs": [
        [["link"], ".link { ...}"],
        [["hidden", "link"], ".hidden a.link { ...}"],
        ...
    ]
}


使用此输出,我们可以创建一个我们使用Trie生产的CSS的内存中表示形式。然后,我们收集了页面上使用的CSS类列表(通常是动态的),这是发生在这样的特定组件中:

h.Div(class_=['link', 'primary'])


最后,给定一个CSS类的列表,我们可以有效地查询内存中的trie,以此获取网页的关键CSS,然后将其内联在页面上。现在,每一个组块都包含它自己的关键CSS内联,这意味着它可以被直接浏览器“绘制”,而不是等待下载一个外部样式表。采取这一步骤,我们还可以去除重复的CSS规则,所以随后的组块不包括已经由以前的组块内联的规则。这种方法可以确保我们会尽快去的进步的表现。


尽可能并行
我们可以在服务器和客户端上都利用并行度,以此来进一步优化我们chunk-based渲染。


在服务器上,我们一直以并行的方式呈现DOM子树。通过分块传输编码,我们以相互平行的形式更新了我们的并行绘制框架来渲染组块,然后将每一个完成的组块在完成时就马上发送给客户。所以,组块可以被单独地表达,也就是说即使页面上有最慢的组块,所有的组块也不会被阻碍。
回到之前的例子,并行呈现组块要像这样做:

subtree = [
    FeedStoryItemPromise(answer_id=5), # previously FeedStoryItem(aid=5)
    FeedStoryItemPromise(answer_id=6),
    FeedStoryItemPromise(answer_id=7),
    ...
]
 
for directive, data in page.render():
    if isinstance(data, Promise):
        # Blocks until this particular chunk is ready.
        # Other parallelized subtrees are concurrently being
        # rendered too!
    else:
        yield data.render()



在客户端,我们可以通过影响并行度来提高资源下载的效率。在一个简洁页面上加载,以下的步骤会呈现:
1、用户请求的页面;
2、构建服务器端页面;
3、页面发送到客户端;
4、浏览器解析页面;
5、浏览器在页面标头下载CSS,JS和字体;
6、浏览器呈现页面;
7、用户看到页面。


无论是在服务器、网络或客户端上,每个步骤都有大量的空闲时间。通过使用分块传输编码,我们可以在资源下载中引入更多的并行度来消除这种空闲时间。


在一个典型的HTML页面,页面所包含的标头链接到页面要求的其他资源,包括CSS,JavaScript,字节、图片。对于那些有大量内容的网站,下载这些文件通常需要花费大量的时间,这反过来又为页面“绘制”或互动方面增加了大量的时间。下面的图标展示了响应负载和相应的内容下载不平行的情况:



为了提高这些资源下载的并行度,我们可以在一个能让用户在第一时间接收到信息的单独组块上发送页面的标头。当浏览器收到的标头组块时,它可以在整个页面下载(即蓝条)之前开始下载CSS、JavaScript、字节、图片。在这样做时,我们的通过客户端并行获取这些子资源,通过服务器获取网页建设。
下面的波形表格可以展示并行改进:



通过首先发送标头组块,浏览器能够在页面完全下载之前启动其他有利条件的下载。


总结
在这篇文章中,我们讨论了我们改善“绘制”页面时长的方法。总结一下:


1、我们做出改变,让我们的服务器响应部分页面,只要他们准备使用分块传输编码。
2、我们改变了关键的CSS实现来支持组块的反应。
3、我们将这些变化与我们并行绘制框架结合起来,进一步利用并行度。


自从我们开始展开这些变化,我们已经看到页面的Speed Index提高了20%左右。我们也观察到,改进流水线操作后,页面的加载速度提高了5%左右。为了让Quora变得更快,我们还有很多事情可以做。更多的项目来提高我们已经在进行中的网站和移动产品的性能。如果你对Quora的工作性能感兴趣,可以从我们的网站了解相关操作规则!

 

欢迎移步解放号论坛,更多活动邀您参加~

 

------------好久不见的分隔线------------


 杰微刊旨在分享优质的内容。
我们水平有限,但理想高远。
也同样期待有理想的您对这个世界的贡献。
欢迎任何目的的联系。

欢迎关注我们



分享到:
赚钱
喜欢
精品汇 精品汇总

手机应用大起底:APP如何让用户习惯成瘾?

随着手机APP应用的全面开花,如何让越来越多的用户上钩、让用户形成一种习惯,已经成为科技公司们最痴迷的一件事情。而手机的小小屏幕,用户的注意力只放在几个常用的APP上,在排队、喝咖啡、吃饭时,情不自禁地打开这些APP,究竟这些APP应该具有怎样的魔力?让我们从开发者的角度来一一探究

评论
*

还可以输入140个字符

提交
全部评论(条)

点击这里,查看赚钱机会