PRPL 是一种用于结构化和交付 Web 应用程序和渐进式 Web 应用(PWA)的模式,重点在于改进应用的交付和启动性能。这个模式包含一组步骤,以实现快速、可靠、高效的加载:
Push,推送初始路由所需的所有资源,并且只推送这些资源,以确保它们尽早可用。
Render,在加载其他资源之前,渲染初始路由并使其具备交互能力。
Pre-cache,预缓存用户可能访问的其他路由的资源,从而在恶劣的网络条件下尽可能提高对后续请求的响应能力和弹性。
Lazy-load,当用户请求时,按需延迟加载路由;关键路由的资源应立即从缓存中加载,而相对不常用的资源可以根据请求从网络获取。
注意:PRPL 模式是由 Polymer 团队于 2016 年首次引入的,但已被证明适用于其他许多技术栈。
PRPL 模式加载顺序
服务器和服务 Worker 一起为非活动路由预缓存资源。当用户切换路由时,应用会延迟加载尚未缓存的所有必需资源,并创建所需的视图。
Twitter.com 自 2017 年以来就在生产中使用 PRPL 模式了。下面我们可以看到,他们对关键脚本使用了粒度代码拆分,并使用<linkrel=preload>推送脚本以尽快让脚本可用:
PRPL 模式:预加载关键脚本
其他路由会按需延迟加载。Twitter 在整个用户体验部分中会按需提供 40 多个块。Twitter 还使用服务 Workers 对其他路由进行(离线)资产预缓存,以提高对后续导航操作的响应能力:
PRPL 模式:离线缓存资源
他们的应用程序外壳程序(骨架 UI)也是离线缓存的,就算用户通过缓慢或不稳定的网络连接加载站点,也会立即加载它们:
PRPL 模式:应用程序外壳
为什么选择 PRPL?
应用使用 PRPL 构建是为了达到可靠、快速和引人入胜的目的。除了这些基本目标,PRPL 还旨在:
改善应用的交互就绪水平。为了做到这一点,在第一个视图渲染并具备交互能力之前,不会向浏览器发送多余的资源。
提高应用的缓存效率,尤其是长期缓存效率。为了做到这一点,资源会以很细的粒度向浏览器发送。当资源被解绑或松散地打包时,对代码的每次更改给缓存的负面影响也会减少。
降低开发和部署的复杂性。为了做到这一点,这种模式使用应用的隐式依赖图将每个入口点精确映射到所需的资源集,从而减少或消除了手动管理打包和交付的需求。
这是一种很有意义的理念,因为今天典型的应用太过臃肿了。为了帮助开发人员在移动 Web 端提供更好的体验,我们首先要做的是让应用变轻变小。这意味着我们要理解并仔细考虑包含在应用中所有内容的权重——包括我们自己的代码及其依赖项。
但这还不够。我们还在以低效的方式交付应用,通常会把应用的全部资源打进一个包里。用户必须在客户端上完整地接收并处理这个包后,才能开始操作。展望未来,我们在构建和提供应用时需要做到:
根据用户的初始请求,我们只交付和处理支持所请求路由的那些资源,以让应用尽早准备好交互;
交互就绪后,我们将开始提供其他必要的资源,以确保应用能够立即响应用户的后续请求;
应用的未来更新应最大限度地提高效率,并消耗尽可能少的带宽和时间。
PRPL 应包含哪些技术?
PRPL 是一种能以各种方式实现的概念模式,但是通过以下现代 Web 特性的某种组合,可以最轻松有效地实现 PRPL:
像 JavaScript Modules 这样的现代模块系统,使工具可以轻松构造完整的依赖图;
服务 Workers,为后续的应用视图预缓存(“安装”)资源(进而支持离线优先架构)
预加载,用于尽快交付所需资源。你还可以使用预加载链接头,这些头可以被协作服务器拦截并升级为 HTTP/2 Server Push(详情可见原文)。一定要记住,尽管 Push 功能强大,但它也面临着许多已知的挑战;不过 PRPL 使用服务 Worker 可以避免过度推送的问题(仅针对初始负载使用 push)。
如何实现 PRPL 模式?
PRPL 的很大一部分理念是对 JS 打包思维的颠覆,并在提供资源时拆分成尽可能接近编写资源时的粒度(至少拆分成独立的功能模块)。那么如何实现细粒度呢?
基于路由或基于组件的代码拆分和延迟加载
你正在将事物编写为组件。也许你正在使用 ES 模块。对于 Webpack,我们使用动态导入和代码拆分,将你的代码库拆分为按需加载的块。
Next.js 和 Nuxt.js 之类的元框架会默认实现基于路由的代码拆分。如果你使用的是 create-react-app 之类的工具链样板,则需要借助 React Router 之类的路由器进行动态导入,才能将基于路由或基于组件的代码拆分添加到你的应用程序中。
对于 PRPL 的 push/preload 部分,Webpack 还支持将 preload 作为魔术注释来预加载关键脚本。
预缓存
可以使用服务 worker 预缓存剩余的路由。另一种常见的做法是,利用 Workbox 之类的服务 worker 库来简化为应用程序预缓存路由和块的过程。
PRPL 使用何种应用程序结构?
PRPL 鼓励采用以下结构的单页应用(SPA)架构:
从每个有效路由提供的应用程序的主入口点。
这个文件应该很小,因为它将通过不同的 URL 提供,因此会被多次缓存。入口点中的所有资源 URL 都必须是绝对的,因为它可以从非顶级 URL 提供。
外壳(app-shell),其中包括顶级应用逻辑、router 等。
延迟加载的应用片段。
可以代表特定视图代码的片段,或其他可以延迟加载的代码(例如,主应用的一些部分在第一次绘制时并不需要,比如说菜单,直到用户与应用交互时才显示)。外壳负责根据需要动态导入这些片段。
应用外壳骨架模式
应用应根据需要调用动态导入以延迟加载片段。例如,当用户更改为新路由时,它将导入与该路由关联的片段。这可能会向服务器发起新请求,或者只是从缓存中加载资源。
小结
除了针对 PWA 的基本目标和标准之外,PRPL 还尽量针对以下方面做了优化:
尽量减少交互的准备工作——特别是在首次使用时(无论入口点如何)。
尽量提升缓存效率,尤其是更新不断发布后的长期缓存效率。
简化开发和部署。
自 2016 年诞生以来,PRPL 模式已获得了大规模使用,值得你在优化应用加载时考虑。
评论