让我们的应用程序在全球范围内可访问可能是一个挑战!我们必须确保该应用程序在低端设备和互联网连接较差的地区性能良好。为了确保我们的应用程序能够在困难的条件下尽可能高效地加载,我们可以使用 PRPL 模式。
PRPL模式是一种用于优化Web应用性能的模式,它包括四个步骤:
1. **预取(Push)**:在加载应用程序之前,将关键资源预取到缓存中,以加快加载速度。这可以通过HTTP/2服务器推送等技术来实现。
2. **渲染(Render)**:在加载过程中,尽早渲染首屏内容,以提供快速的视觉反馈。这通常涉及到使用服务端渲染(SSR)或者骨架屏技术。
3. **预加载(Pre-cache)**:预加载应用程序中未来可能需要的资源,以减少后续页面间导航的加载时间。可以通过Service Worker来实现资源的预加载。
4. **懒加载(Lazy-load)**:延迟加载非关键资源,以减少初始加载时间。这可以通过按需加载JavaScript模块、图片懒加载等技术来实现。
PRPL模式的目标是通过优化资源加载顺序和时间点,从而提高Web应用程序的性能和用户体验。
当我们想访问一个网站时,我们首先必须向服务器发出请求才能获取这些资源。入口点指向的文件从服务器返回,通常是我们应用程序的初始 HTML 文件!浏览器的 HTML 解析器在开始从服务器接收此数据后立即开始解析此数据。如果解析器发现需要更多资源,例如样式表或脚本,则会向服务器发送另一个 HTTP 请求以获取这些资源!
必须重复请求资源并不是最佳选择,因为我们正在尝试最大程度地减少客户端和服务器之间的往返次数!
很长一段时间以来,我们使用 HTTP/1.1 来在客户端和服务器之间进行通信。尽管与 HTTP/1.0 相比,HTTP/1.1 引入了许多改进,例如能够在使用 keep-alive
标头发送新的 HTTP 请求之前保持客户端和服务器之间的 TCP 连接处于活动状态,但仍有一些问题需要解决!
与 HTTP/1.1 相比,HTTP/2 引入了一些重大更改,这使我们能够更轻松地优化客户端和服务器之间的消息交换。
HTTP/1.1 在请求和响应中使用换行符分隔的明文协议,而 HTTP/2 将请求和响应拆分为更小的部分,称为帧。包含标头和正文字段的 HTTP 请求至少被拆分为两个帧:标头帧和数据帧!
HTTP/1.1 在客户端和服务器之间最多有 6 个 TCP 连接。在通过同一 TCP 连接发送新请求之前,必须解决以前的请求!如果上一个请求需要很长时间才能解决,则此请求将阻止发送其他请求。这个常见的问题称为线头阻塞,可能会增加某些资源的加载时间!
HTTP/2 使用双向流,这使得拥有一个包含多个双向流的单个 TCP 连接成为可能,这些双向流可以在客户端和服务器之间承载多个请求和响应帧!
一旦服务器收到该特定请求的所有请求帧,它就会重新组合它们并生成响应帧。这些响应帧将发送回客户端,客户端会重新组合它们。由于流是双向的,因此我们可以通过同一流发送请求和响应帧。
HTTP/2 通过允许在前一个请求解析之前在同一 TCP 连接上发送多个请求来解决行头阻塞问题!
HTTP/2 还引入了一种更优化的数据获取方式,称为服务器推送。服务器不必每次都通过发送 HTTP 请求来显式请求资源,而是可以通过“推送”这些资源来自动发送其他资源。
客户端收到其他资源后,这些资源将存储在浏览器缓存中。当在解析入口文件时发现资源时,浏览器可以快速从缓存中获取资源,而不必向服务器发出 HTTP 请求!
虽然推送资源减少了接收额外资源的时间,但服务器推送无法识别 HTTP 缓存!下次我们访问网站时,我们将无法使用推送的资源,必须再次请求。为了解决这个问题,PRPL 模式在初始加载后使用 Service Worker 来缓存这些资源,以确保客户端不会发出不必要的请求。
作为网站的作者,我们通常知道哪些资源在早期获取至关重要,而浏览器则尽最大努力猜测这一点。幸运的是,我们可以通过向关键资源添加preload
资源提示来帮助浏览器!
通过告诉浏览器您要预加载某个资源,您就是告诉浏览器您希望在浏览器发现它之前更快地获取它!预加载是优化加载对当前路由至关重要的资源所需的时间的好方法。
尽管预加载资源是减少往返次数和优化加载时间的好方法,但推送过多文件可能是有害的。浏览器的缓存是有限的,你可能会通过请求客户端实际上不需要的资源来不必要地使用带宽。
PRPL模式侧重于优化初始负载。在初始路由加载并完全渲染之前,不会加载其他资源!
我们可以通过将应用程序代码拆分为小型高性能捆绑包来实现这一点。这些捆绑包应该使用户能够在需要时只加载他们需要的资源,同时最大限度地提高可缓存性!
缓存较大的 bundle 包可能是一个问题。多个 bundle 包可能会共享相同的资源。
浏览器很难识别 bundle 包的哪些部分在多个路由之间共享,因此无法缓存这些资源。缓存资源对于减少与服务器的往返次数以及使我们的应用程序离线友好非常重要!
在使用 PRPL 模式时,我们需要确保我们请求的 bundle 包包含我们当时需要的最少资源,并且浏览器可以缓存。在某些情况下,这可能意味着完全没有 bundle 包会更高性能,我们可以简单地使用非 bundle 模块!
通过配置浏览器和服务器以支持 HTTP/2 推送并有效地缓存资源,可以很容易地通过 bundle 应用程序来动态请求最少资源的好处。对于不支持 HTTP/2 服务器推送的浏览器,我们可以创建一个经过优化的构建,以最大程度地减少往返次数。客户端不必知道它是接收 bundle 资源还是非 bundle 资源:服务器为每个浏览器提供适当的构建。
PRPL 模式通常使用应用程序 shell 作为其主要入口点,这是一个包含应用程序大部分逻辑并在路由之间共享的最小文件!它还包含应用程序的路由器,它可以动态请求必要的资源。
PRPL 模式可确保在初始路由在用户设备上可见之前,不会请求或呈现任何其他资源。成功加载初始路由后,可以安装服务器工作线程,以便在后台获取其他经常访问的路由的资源!
由于此数据是在后台获取的,因此用户不会遇到任何延迟。如果用户想要导航到 Service Worker 缓存的频繁访问的路由,则 Service Worker 可以快速从缓存中获取所需的资源,而不必向服务器发送请求。
可以动态导入不经常访问的路由的资源。
实施 PRPL 模式涉及以下步骤:
- **推送关键资源:**使用 HTTP/2 Server Push 或 Link preload 标头推送关键资源。
- **预取其他资源:**使用 Link rel="prefetch" 标头预取其他资源。
- **分块加载资源:**使用脚本加载模块或 HTTP/2 推送来分块加载资源。
- **优化资源顺序:**确保关键资源比非关键资源更早加载。
工具和库
有许多工具和库可以帮助实施 PRPL 模式,包括:
- **webpack (webpack.js.org):**一个打包工具,可以分块加载资源。
- **http-server-push (github.com/http-party/http-server-push):**一个 Node.js 库,用于推送关键资源。
- **link-prefetch (github.com/jeffposnick/link-prefetch):**一个用于预取资源的 JavaScript 库。
示例
以下是一个使用 webpack 和 http-server-push 实现 PRPL 模式的示例:
// webpack.config.js
module.exports = {
// 分块加载资源
optimization: {
splitChunks: {
chunks: "all",
minSize: 30000,
maxSize: 70000,
},
},
plugins: [
// 推送关键资源
new HttpServerPushPlugin({
pushAll: true,
}),
],
};
<!-- index.html -->
<head>
<!-- 推送关键 CSS -->
<link rel="preload" href="styles.css" as="style" />
<!-- 渲染 HTML -->
<div id="root"></div>
</head>
<body>
<script>
// 预取其他脚本
const prefetchScript = document.createElement("link");
prefetchScript.rel = "prefetch";
prefetchScript.href = "scripts.js";
document.head.appendChild(prefetchScript);
</script>
</body>
通过遵循 PRPL 模式和使用适当的工具和库,可以显著提高网页性能和用户体验。