异步碎片化:重新探索Progressive HTML渲染引擎

在ebay, 我们高度重视网站的性能和加载速度, 我们总是希望开发人员能够开发出被高速加载的网页. 这一点促使我们需要很好的了解网页内容是如何分发内容给浏览器的. progressive html渲染技术的应用在优化我们的网站上来讲已经是一个相对来说比较陈旧的话题了,但是它却在目前越来越多的新技术中被淹没了. 这项技术很简单,就是早点刷新(flushing)与浏览器之间建立的数据流,并且在结束前刷新多次. 浏览器本来就拥有很好的从服务器端解析和响应html数据流的能力(这里指的是在响应已经结束之前). 这个功能允许html和外部的资源文件(css, js等)被提早的下载, 并且提前渲染一部分的页面. 这样一来,实际加载时间和感觉上的加载时间都得到了提高.
在本篇博文中,我们将会深入了解这项技术,我们叫它:”异步碎片化(*async fragments*)”, 它将会使用progressive html的概念来做到非常快捷简易的提高网站页面响应的速度. 这里的例子将会用到 node.js, express.js和marko模板引擎(*marko是一个支持流(streaming), 数据刷新(flushing)和异步渲染(asynchronous rendering)的javascript模板引擎*). 即时你不使用以上这些技术,这篇博文也会让你从核心概念上帮助到你.
历史背景progressive html渲染在这篇2005年由jeff atwood发布的博文the lost art of progressive html rendering中就有过探讨. 另外,雅虎(yahoo)公司的性能团队在他们的一份指南best practices for speeding up your web site中, 也曾发布过关于”提早刷新缓存(*flush the buffer early*)”的相关规则. stoyan stefanov提供了一篇叫做progressive rendering via multiple flushes的博文,深入讲解progressive html技术. 脸谱(facebook)公司也探讨过他们怎样使用他们的“bigpipe”技术来提高页面的相应时间以及实际的性能上的感受,他们的做法是把页面分割为”pagelets”. 这些文章中所使用到的技术和观点也是本片博文的灵感所在.
没有progressive html渲染页面的渲染将会比较缓慢如果没有使用progressive html的方式,这是因为所有的字节(bytes)直到相应的请求结束后才会被刷新(flushing). 另外,当客户端最后收到了完整的html后,它才会开始下载一些额外的静态资源(例如: css, javascript, 字体文件以及图片), 并且下载这些其他的资源将会需要额外的开销. 再者,如果页面不使用progress html技术还会造成用户感知的页面读取时间过长, 因为在浏览器屏幕不会发生变化直到完整的html被下载完成并且<head>中的css和字体文件被下载完成. 在没有progressive html渲染的时候,一个客户端/服务器端的瀑布模型图将会如下所示:
假定对应的控制层如下:
function controller(req, res) { async.parallel([ function loadsearchresults(callback) { ... }, function loadfilters(callback) { ... }, function loadads(callback) { ... } ], function() { ... var viewmodel = { ... }; res.render('search', viewmodel); }) } 这里我们可以看到,页面只有当所有的异步数据都加载完成后才会被渲染.
这是因为html没有被刷新(flushing)直到所有的服务器端服务执行完成, 这样一来,用户将会持续在一开始的时候看到一个空白的页面很久, 这将会造成很不好的用户体验(尤其处于网络不好,或者后台服务器执行很慢的时候). 但实际上我们可以通过提前刷新(flushing)数据流来提供更好的体验.
提前刷新头部一个很简单的技巧来提高网站的相应体验就是通过刷新(flushing)页面的<head>. 因为<head>中包含了一些必要的外部资源(例如<link>标签), 以及标题栏和导航, 这样将会使得外部css被很快的下载并且浏览器开始画出初始的页面,如下图所示:
如上图所示,刷新(flushing)头部将会减少相应时间并渲染出初始页面. 这种方式提高了页面的响应速度, 但是却没有明确的减少整个页面变得完全可用所花费的时间. 在使用这种方式时,服务器还是在等待所有的后端服务完成后才会刷新(flushing)最终的html. 另外,下载外部的javascript资源将会延迟至<script>标签在页面的最后被渲染出来之后.
多重刷新(flushes)相对于只是前提刷新头部,更有益的方式无疑是在相应结束前做多重刷新(*multiple flushes*). 实际上,一个页面可以被分割为多个碎片(fragments), 并且这些碎片中的一部分可能依赖于异步加载的后端服务,但其他的可能不是依赖于任何的后台数据. 这些依赖于后端加载的异步数据就应该被异步化的加载并尽可能快的刷新(flushing)并渲染.
目前来说,我们可以确定的是这些碎片需要被按照html属性的顺序来做刷新(可是数据却是异步加载的), 但是我们需要解决的是怎样可以让无序(out-of-order)的刷新(flushing)可以用来解决页面的加载时间和用户实际感受的时间. 当使用有序(in-order)刷新时,由于碎片是完全无序的所以需要被缓存起来直到他们已经准备好被刷新给有序的html属性.
译者注:这里比较绕,如果不明白就接下往下看.
异步碎片的有序刷新(in-order)举例说明, 让我们假定我们有一个被分割的复杂页面有一下这些碎片(fragments):
每一个碎片都基于他们将会在文档上出现的顺序安排了一个编号. 我们的html代码输入可能如下所示:
<html> <head> <title>clothing store</title> <!-- 1a) head <link> tags --> </head> <body> <header> <!-- 1b) header --> </header> <div class=body> <main> <!-- 2) search results --> </main> <section class=filters> <!-- 3) search filters --> </section> <section class=ads> <!-- 4) ads --> </section> </div> <footer> <!-- 5a) footer --> </footer> <!-- 5b) body <script> tags --> </body> </html> marko的模板引擎提供了一种方式可以直接绑定模板碎片给后端的异步数据提供者的函数(function)或者是promise对象. 某一个异步的碎片将会在异步数据提供的回调函数被调用时渲染. 如果说这个异步碎片已经准备好被刷新(flushing), 那么它将会立即被刷新到流(streaming)中. 否则, 如果异步碎片在完成时是无序的,那么被渲染的html将会被缓存起来直到它也已经准备好被刷新. marko的模板引擎将会最终确保所有的碎片都是按照html的属性顺序来刷新的.
继续上面的例子,我们的html页面的模板附上异步碎片后将会如下所示:
<html> <head> <title>clothing store</title> <!-- head <
上一个:网站导航怎么设计用户体验比较好,网站制作时导航要怎么设计
下一个:网站建设,选择一个好的域名很重要!
安龙网站建设,安龙做网站,安龙网站设计