PRPL 模式
作者:Addy Osmani
Dogfood:PRPL 是我们觉得非常有潜力的一种新模式。在这一阶段,我们欢迎使用这一模式进行实验,反复在模式中测试我们的创意并收集此模式在哪些方面提供最大便利性的更多数据。
移动网络非常慢。这些年,网络已从以文档为中心的平台演化为一流的应用平台。 有赖于平台本身的进步(例如服务工作线程)以及我们用于构建应用的工具和技术,用户在网络上几乎可以通过虚拟方式执行任何操作,就像在本机应用中操作一样。
同时,我们大量的计算也已经发生变化,从使用快速、稳定网络连接的强大桌面设备转移到连接经常较慢、不稳定(或两者兼有)的相对欠强大的移动设备上。特别是在孕育着下一批十亿用户的地方,这一点体现得尤为真切。
很遗憾,我们在桌面时代设计用于构建和部署强大、功能丰富的网络应用的模式通常会导致应用在移动设备上的加载时间过长 - 漫长的时间让很多用户选择放弃应用。
这为创建新模式提供了机会,新模式需要利用现代网络平台功能更快速、更精细地提供移动网络体验。PRPL 就是这样一种模式。
PRPL 模式
PRPL 是一种用于结构化和提供 Progressive Web App (PWA) 的模式,该模式强调应用交付和启动的性能。 它代表:
- 推送 - 为初始网址路由推送关键资源。
- 渲染 - 渲染初始路由。
- 预缓存 - 预缓存剩余路由。
- 延迟加载 - 延迟加载并按需创建剩余路由。
除了针对 PWA 的基本目标和标准外,PRPL 还竭力在以下方面进行优化:
- 尽可能减少交互时间
- 特别是第一次使用(无论入口点在何处)
- 特别是在真实的移动设备上
- 尽可能提高缓存效率,特别是在发布更新时
- 开发和部署的简易性
PRPL 的灵感来源于一套现代网络平台功能,不必在首字母缩略词中打出每个字母或使用每个功能就可以应用这一模式。
实际上,PRPL 更多的是一种思维模式和提高移动网络性能的长期愿景,而不仅仅是特定技术或技巧。PRPL 背后的理念并不新,但该方法由 Polymer 团队构建框架和命名,并在Google I/O 2016上公布。
Polymer 的Shop电子商务演示是使用 PRPL 精细地提供资源的一流示例。它在真实的移动设备上以不可思议的速度为每条路由实现交互性:
对于大多数真实项目,要纯粹、完整地实现 PRPL 愿景还为时过早 - 但接受这种思维模式,或开始从多个角度追求这种愿景绝对不早。在追寻 PRPL 的今天,应用开发者、工具开发者与浏览器厂商可以采取许多实用步骤。
应用结构
如果您的单页面应用 (SPA) 采用以下结构,PRPL 完全适用:
- 应用的主_进入点_从每个有效的路由提供。 此文件应非常小,它从不同网址提供,因此会被缓存多次。 进入点的所有资源网址都需要是绝对网址,因为它可以从非顶级网址提供。
- Shell 或 App Shell,包含顶级应用逻辑、路由器,等等。
- 延迟加载的应用_片段_。片段可以表示特定视图的代码,或可延迟加载的其他代码(例如,首次绘制不需要的部分主应用,如用户与应用交互前未显示的菜单)。Shell 负责在需要时动态导入片段。
服务器和服务工作线程协同为非活动路由预缓存资源。
用户切换路由时,应用会延迟加载尚未缓存的任何所需资源,并创建所需视图。 路由重复访问应当可以立即交互。 服务工作线程这时可以提供很大帮助。
下图显示了使用网络组件构建的一个简单应用的组件:
注:尽管 HTML 导入是 Polymer 的首选捆绑策略,您也可以使用代码拆分和基于路由的分块,通过现代 JavaScript 模块捆绑程序实现相似的设置
在此图表中,实线表示_静态依赖项_:使用<link>
和<script>
标记在文件中标识的外部资源。 虚线表示_动态_或_按需加载的依赖项_:根据 Shell 所需加载的文件。
构建过程会构建一个包含所有这些依赖项的图表,服务器会使用此信息高效地提供文件。 还会为不支持 HTTP/2 的浏览器构建一组硬化捆绑包。
应用进入点
进入点必须导入和实例化 Shell,并且有条件地加载任何所需的 polyfill。
进入点的主要注意事项为:
- 具有最少的静态依赖项,换言之,除了 App Shell 本身外并没有多少依赖项。
- 有条件地加载所需的 polyfill。
- 为所有依赖项使用绝对路径。
App Shell
Shell 负责路由,通常包含应用的主导航 UI。
应用会根据需要延迟加载片段。例如,当用户更换到新路由时,应用会导入与该路由相关的片段。这可能会向服务器发出新请求,或者只是从缓存加载资源。
Shell(包括其静态依赖项)应包含第一次绘制所需的全部资源。
构建输出
尽管使用 PRPL 没有硬性要求,但您的构建过程仍会生成两个构建:
- 设计用于服务器/浏览器组合的未捆绑构建,此类组合支持 HTTP/2,以便在优化缓存时传输第一次快速绘制所需的资源。可以使用
<link rel="preload">
或 HTTP/2 Push 高效地触发这些资源的传输。 - 设计用于最大程度减少所需往返次数的捆绑构建,使应用在不支持服务器推送的服务器/浏览器组合上运行时需要往返。
您的服务器逻辑会为每个浏览器传输合适的构建。
捆绑的构建
对于不处理 HTTP/2 的浏览器,构建过程会生成一组不同的捆绑包:一个用于 Shell 的捆绑包,以及一个用于每个片段的捆绑包。下面的图表显示了如何使用网络组件捆绑一个简单的应用:
两个或多个片段共享的任何依赖项与 Shell 及其静态依赖项相捆绑。
每个片段及其_未共享的_静态依赖项捆绑到一个单独的捆绑包中。 服务器应当返回合适版本的片段(捆绑或未捆绑),具体取决于浏览器。 这意味着 Shell 代码可以延迟加载detail-view.html
,无需了解是捆绑还是未捆绑。它依靠服务器和浏览器来尽可能高效地加载依赖项。
背景:HTTP/2 和 HTTP/2 服务器推送
HTTP/2允许在单个连接上_复用_下载,所以能够更高效地下载多个小文件。
HTTP/2 服务器推送允许服务器抢先向浏览器发送资源。
以 HTTP/2 服务器推送如何加速下载为例,考虑浏览器如何使用关联的样式表检索 HTML 文件。
在 HTTP/1 中:
- 浏览器请求 HTML 文件。
- 服务器返回 HTML 文件,然后浏览器开始解析。
- 浏览器遇到
<link rel="stylesheet">
标记,启动对样式表的新请求。 - 浏览器接收样式表。
对于 HTTP/2 推送:浏览器请求 HTML 文件。服务器返回 HTML 文件,同时推送样式表。 * 浏览器开始解析 HTML。在浏览器遇到 `之前,样式表已经位于缓存中。
在最简单的情况下,HTTP/2 服务器推送可以去除一个 HTTP 请求响应。
在 HTTP/1 中,开发者可以捆绑资源来减少渲染页面所需的 HTTP 请求数。 不过,捆绑会降低浏览器缓存的效率。如果每个页面的资源整合到一个捆绑包中,每个页面会获得自己的捆绑包,浏览器将无法识别共享的资源。
结合 HTTP/2 和 HTTP/2 服务器推送带来了捆绑的_好处_(延迟时间减少),而无需真正捆绑。 保持资源独立意味着它们可以被高效地缓存且可以在页面间共享。
需要小心使用 HTTP/2 推送,因为即使文件已经位于浏览器的本地缓存中或带宽已饱和,它都会对浏览器强制推送数据。如果操作错误,性能将降低。<link rel="preload">
能够有效地使浏览器对设置这些请求的优先级做出明智的决定。
结论
更精细地为路由加载代码和让浏览器更好地安排工作时间有助于在应用中更早实现交互性。我们需要可以快速实现交互性的更优架构,PRPL 模式是一个有趣的示例,它描绘了如何在真实的移动设备上实现此目标。
PRPL 模式很好地解决了余量问题,在您完成抽象的加载后,它可以立即为您提供足够余量。 如果点按某个链接时因为阻止输入事件分派的数秒脚本而出现延迟,这就是一个需要优化性能的强烈信号。眼下,使用较大的 JavaScript 库构建的应用存在一个常见问题,UI 虽然正确渲染,但并不能按预期工作。
PRPL 仅需最少的功能代码即可让用户所在的路由变得可交互,切实解决了这一问题。