若要设计应用以充分利用可让 PWA 可靠、可安装且功能强大的技术,首先要了解应用及其限制,并为二者选择合适的架构。
SPA 与 MPA
如今,Web 开发中主要有两种架构模式:单页应用 (SPA) 和多页应用 (MPA)。
单页面应用的定义是:客户端 JavaScript 会根据应用检索或提供的数据控制网页的大部分或全部 HTML 呈现。该应用会替换浏览器的内置导航栏,将其替换为自己的路由和视图处理功能。
多页面应用通常会将预渲染的 HTML 直接发送到浏览器,通常在浏览器加载完 HTML 后使用客户端 JavaScript 进行增强,并依赖于浏览器的内置导航机制来显示后续视图。
这两种架构都可用于创建 PWA。
每种方法都有优缺点,为用户提供快速可靠的体验的关键在于根据您的用例和上下文选择合适的方法。
单页应用
- 主要为原子页内更新。
- 启动时加载的客户端依赖项。
- 由于使用了缓存,后续加载速度很快。
- 初始加载费用较高。
- 性能取决于设备硬件和网络连接。
- 需要增加应用复杂性。
在以下情况下,单页应用是理想的架构设计方案:
- 用户互动主要围绕同一页面上显示的相互关联数据的原子更新展开,例如实时数据信息中心或视频编辑应用。
- 您的应用具有客户端专用初始化依赖项,例如启动成本极高的第三方身份验证提供程序。
- 视图加载所需的数据依赖于特定的仅限客户端上下文,例如,显示某个已连接硬件的控件。
- 应用足够小而简单,其大小和复杂程度不会对上面列出的缺点产生影响。
如果您符合以下条件,SPA 可能不是一个好的架构选择:
- 初始加载性能至关重要。SPA 通常需要加载更多 JavaScript 来确定要加载的内容以及如何显示这些内容。与发送所呈现的 HTML 相比,此 JavaScript 的解析和执行时间以及检索内容的时间都要慢。
- 您的应用主要在低功耗到中等功耗的设备上运行。由于 SPA 依赖于 JavaScript 进行渲染,因此与 MPA 相比,用户体验在很大程度上取决于用户所用特定设备的性能。
由于 SPA 需要将浏览器的内置导航替换为其路由,因此在高效更新当前视图、管理导航更改以及清理原本由浏览器处理的先前视图方面,SPA 需要尽可能降低复杂性,这使得它们总体上更难维护,并且会给用户设备带来更大的负担。
多页应用
- 主要进行全页更新。
- 初始渲染速度至关重要。
- 客户端脚本可作为一种增强功能。
- 辅助视图需要进行另一个服务器调用。
- 上下文不会在视图之间传递。
- 需要服务器或预渲染。
在以下情况下,多页应用是不错的架构选择:
- 用户互动主要围绕单个数据的视图展开,其中包含可选的基于上下文的数据,例如新闻应用或电子商务应用。
- 初始呈现速度至关重要,因为将已呈现的 HTML 发送到浏览器比在加载、解析和执行基于 JavaScript 的替代方案之后通过数据请求对其进行汇编的速度更快。
- 在初始加载后,可以将客户端互动性或上下文作为增强功能添加,例如将配置文件叠加到已呈现的网页上或添加依赖于客户端上下文的次级组件。
在以下情况下,MPA 可能不是好的架构选择:
- 重新下载、重新解析和重新执行 JavaScript 或 CSS 的成本非常高昂。在使用 Service Worker 的 PWA 中,可以缓解这一缺点。
- 客户端上下文(例如用户位置信息)无法在视图之间无缝传递,并且重新获取该上下文的开销可能很高。它要么需要捕获并检索,要么需要在视图之间重新请求。
由于各个视图需要由服务器动态呈现或在访问之前预呈现,这可能会限制托管或增加数据的复杂性。
选择哪个?
即使有这些优缺点,这两种架构依然可用于创建 PWA。您甚至可以根据应用的需求,在应用的不同部分混合使用这两种架构,例如,让商品详情采用 MPA 架构,而结账流程采用 SPA 架构。
无论您选择哪种方式,下一步都是了解如何最佳地使用服务工件来提供最佳体验。
Service Worker 的强大功能
除了基本的路由和传递缓存响应和网络响应之外,Service Worker 还具有许多功能。我们可以创建复杂的算法,以提升用户体验和效果。
Service Worker Includes (SWI)
一种将服务工作器用作网站架构不可或缺的一部分的新兴模式是服务工作器包含 (SWI)。SWI 会根据各个资源(通常是 HTML 页面)的缓存需求将其拆分成多个部分,然后在服务工件中将其拼接回来,以提高一致性、性能和可靠性,同时缩减缓存大小。
此图片是一个示例网页。该页面包含五个不同的部分,分别是:
- 整体布局。
- 全局标题(顶部深色栏)。
- 内容区域(左侧中间的线条和图片)。
- 边栏(右侧中间高深的中等深色条)。
- 页脚(深色底部栏)。
整体布局
整体布局不太可能经常更改,并且没有依赖项。非常适合执行预缓存。
页眉和页脚
全局页眉和页脚包含顶部菜单和网站页脚等内容,这就带来了一个特殊的挑战:如果将页面作为一个整体进行缓存,则这些在页面加载期间可能会发生变化,具体取决于给定页面的缓存时间。
通过将它们分开并独立于内容进行缓存,您可以确保无论何时缓存,用户始终会获得相同的版本。由于这些资源更新频率较低,因此也非常适合预缓存。不过,它们有一个依赖项:网站的 CSS 和 JavaScript。
CSS 和 JavaScript
理想情况下,应采用“在重新验证时使用过时资源”策略来缓存网站的 CSS 和 JavaScript,以便在不更新服务工件的情况下进行增量更新(就像预缓存的资源一样)。不过,每当服务工件使用新的全局标题或页脚进行更新时,它们也需要保持最低版本。因此,在 Service Worker 安装时,其缓存也应更新为最新版本的资源。
内容区域
接下来是内容区域。根据更新频率,网络优先或在重新验证时使用过时数据都是不错的策略。如前文所述,应该使用缓存优先策略来缓存图片。
边栏
最后,假设边栏内容包含标签和相关项等辅助内容,则从网络中提取这些内容并不至关重要。过时的重新验证策略适用于这种情况。
现在,了解了所有这些之后,您可能认为只能对单页应用执行这种按部分缓存。但是,通过在 Service Worker 中采用受边缘包含或服务器端包含启发的模式,以及一些高级 Service Worker 功能,您可以对任一架构执行此操作。
亲自尝试
您可以在下一个 Codelab 中试用 Service Worker 包含的内容:
流式响应
在 SPA 环境中,您可以使用 App Shell 模型创建前一个页面,在该模型中,App Shell 会被缓存,然后提供,并在客户端加载内容。随着 Streams API 的推出和广泛使用,应用壳和内容都可以在服务工作器中组合并流式传输到浏览器,从而让您既能享受应用壳的缓存灵活性,又能获得 MPA 的速度。
这是因为:
- 数据流可以异步构建,允许数据流的不同部分来自其他来源。
- 数据流的请求方可以在第一个数据块可用后立即开始处理响应,而无需等待整个内容完成。
- 针对流式传输进行了优化的解析器(包括浏览器)可以在流式传输完成之前逐步显示流式传输的内容,从而加快响应的感知性能。
得益于这三个流的特性,与未采用流的架构相比,基于流的架构通常具有更快的感知性能。
Streams API 既复杂又低级,因此使用它极具挑战性。幸运的是,有一个 Workbox 模块可以帮助您为 Service Worker 设置流式响应。
网域、源和 PWA 范围
网页工作器(包括服务工作器、存储空间,甚至已安装的 PWA 的窗口)都受 Web 上最重要的安全机制之一(同源政策)的约束。在同一源内,授予权限、共享数据,并且 Service Worker 可以与不同的客户端通信。在同一来源之外,系统不会自动授予权限,并且数据是隔离的,不同来源之间无法访问。
同源政策
如果两个网址的协议、端口和主机相同,则定义为具有完全相同的来源。
例如:https://squoosh.app
和 https://squoosh.app/v2
具有相同的来源,但 http://squoosh.app
、https://squoosh.com
、https://app.squoosh.app
和 https://squoosh.app:8080
具有不同的来源。如需了解详情和示例,请参阅同源政策 MDN 参考文档。
更改子网域并不是托管商更改的唯一方式。每个主机都由顶级网域 (TLD)、二级网域 (SLD) 和零个或多个标签(有时称为子网域)组成,这些标签之间以英文句点分隔,并且在网址中从右到左读取。任何项发生更改都会导致主机发生变化。
在窗口管理模块中,我们已经了解了当用户从已安装的 PWA 导航到其他来源时,应用内浏览器的显示效果。
即使网站具有相同的顶级域名 (TLD) 和次级域名 (SLD),但标签不同,系统也会将其视为不同的来源,因此系统会显示该应用内浏览器。
在网络浏览环境中,源的关键方面之一是存储空间和权限的运作方式。一个源会在其中的所有内容和 PWA 之间共享许多功能,包括:
- 存储配额和数据(IndexedDB、Cookie、Web 存储空间、缓存存储空间)。
- Service Worker 注册。
- 授予或拒绝的权限(例如网站推送、地理定位、传感器)。
- Web 推送注册。
从一个源移动到另一个源时,系统会撤消之前的所有访问权限,因此必须重新授予权限,并且您的 PWA 无法访问存储空间中保存的所有数据。