多源网站中的渐进式 Web 应用

在多源网站中构建渐进式 Web 应用所面临的挑战和解决方法。

Demián Renzulli
Demián Renzulli

背景

过去,使用多源架构具有一些优势,但对于渐进式 Web 应用来说,这种方法带来了许多挑战。具体而言,同源政策对共享 Service Worker 和缓存等内容、权限以及跨多个源实现独立体验施加了限制。本文将说明使用多个源的好与坏,并说明在多源网站中构建渐进式 Web 应用所面临的挑战和解决方法。

使用多个来源的好与坏

网站采用多源架构是有正当理由的,这主要是为了提供一组独立的 Web 应用或打造彼此完全隔离的体验。此外,还有一些应用场景是应避免的。

良好

我们先来看看这些实用的原因:

  • 本地化/语言:使用国家/地区代码顶级域名来划分在不同国家/地区提供的网站(例如 https://www.google.com.ar),或使用子域名划分定位到不同地理位置的网站(例如:https://newyork.craigslist.org)或针对特定语言(例如 https://en.wikipedia.org)提供内容。

  • 独立的 Web 应用:使用不同的子网域提供用途与主要来源网站截然不同的体验。例如,在新闻网站上,填字游戏 Web 应用可能会有意通过 https://crosswords.example.com 提供,并作为独立的 PWA 安装和使用,而无需与主网站共享任何资源或功能。

不好

如果不采取上述任何行动,在构建渐进式 Web 应用时,使用多源架构可能会不利。

尽管如此,许多网站仍出于非特定原因或因“遗留”原因而继续采用这种结构。例如,使用子网域将网站中本应属于统一体验的一部分的任意部分分开。

例如,我们强烈建议不要使用以下模式:

  • 网站版块:将网站的不同版块划分到子网域上。在新闻网站中,首页位于 https://www.example.com,而体育版块位于 https://sports.example.com,政治版块位于 https://politics.example.com 等位置,这种情况并不少见。如果是电子商务网站,请为商品类别使用 https://category.example.com,为商品页面使用 https://product.example.com 等。

  • 用户流:另一种不建议使用的方法是将网站的不同部分(例如登录页或购买流程页)划分到子网域中。例如,使用 https://login.example.comhttps://checkout.example.com

对于无法迁移到单一源的情况,下面列出了一系列挑战,以及构建渐进式网页应用时可以考虑的解决方法(如果可能)。

不同源 PWA 面临的挑战和解决方法

在多个源上构建网站时,提供统一的 PWA 体验极具挑战性,主要是因为同源政策存在许多限制。我们一次看一个。

Service Worker

Service Worker 脚本网址的来源必须与调用 register() 的网页的来源相同。这意味着,举例来说,这意味着位于 https://www.example.com 的网页无法使用位于 https://section.example.com 的 Service Worker 网址调用 register()

另一个需要考虑的问题是,Service Worker 只能控制托管在其所属的源和路径下的页面。这意味着,如果 Service Worker 托管在 https://www.example.com 上,则它只能控制来自该源的网址(根据 scope 参数中定义的路径),而无法控制其他子网域(例如 https://section.example.com 中的网页)中的任何网页。

在这种情况下,唯一的解决方法是使用多个 Service Worker(每个源一个)。

缓存

Cache 对象、indexedDB 和 localStorage 也被限制在单个源站内。这意味着无法从 https://www.section.example.com 等位置访问属于 https://www.example.com 的缓存。

在此类情况下,您可以采取以下措施来妥善管理缓存:

  • 利用浏览器缓存:始终建议使用传统的浏览器缓存最佳做法。此技术还具有跨源重复使用已缓存资源的额外优势,这是通过 Service Worker 的缓存无法实现的。如需了解有关如何将 HTTP 缓存与 Service Worker 结合使用的最佳实践,您可以参阅这篇博文

  • 确保 Service Worker 的轻量级安装:如果您要维护多个 Service Worker,请避免让用户在每次导航到新源站时都支付高昂的安装费用。换句话说:仅预缓存绝对必要的资源。

权限

权限也限定为源。这意味着,如果用户向源 https://section.example.com 授予了给定权限,相应权限将不会沿用到其他源,例如 https://www.example.com

由于无法跨源共享权限,因此这里唯一的解决方案是针对需要特定功能(例如位置)的每个子网域请求权限。对于 Web 推送等操作,您可以维护一个 Cookie 来跟踪其他子网域中的用户是否已接受该权限,从而避免再次请求该权限。

安装

如需安装 PWA,每个来源必须拥有自己的清单,其中 start_url 相对于自身。这意味着,在指定来源(即 https://section.example.com)上收到安装提示的用户将无法在其他来源(即 https://www.example.com)上安装具有 start_url 的 PWA。换句话说,在子网域中收到安装提示的用户只能为子页面安装 PWA,而无法为应用的主网址安装 PWA。

还有这样一个问题:如果每个子网域都符合安装条件,那么同一用户在浏览网站时可能会收到多个安装提示,并提示用户安装 PWA。

为了缓解此问题,您可以确保提示仅在主源上显示。如果用户访问符合安装条件的子网域:

  1. 监听 beforeinstallprompt 事件
  2. 阻止显示提示,调用 event.preventDefault()

这样,您可以确保该提示不会显示在网站的意外部分,而您可以继续显示,例如在主要来源(例如首页)中显示。

独立模式

在独立窗口中导航时,当用户移至 PWA 清单设置的范围之外时,浏览器的行为会有所不同。具体行为取决于浏览器版本和供应商。例如,当用户在最新版 Chrome 中退出独立模式时,系统会打开一个 Chrome 自定义标签页

在大多数情况下,我们都没有针对这种情况的解决方法,但对于在子网域中托管的一小部分体验(例如登录工作流),您可以采用临时解决方法:

  1. 新网址 https://login.example.com 可以在全屏 iframe 中打开。
  2. 在 iframe 内完成任务(例如登录流程)后,便可使用 postMessage() 将所有结果信息从 iframe 传递回父页面。
  3. 最后,主页面收到消息后,即可取消注册监听器,并最终从 DOM 中移除 iframe。

总结

同源政策对基于多个源构建的网站施加了许多限制,希望实现连贯的 PWA 体验。因此,为了向用户提供最佳体验,我们强烈建议您不要将网站划分为不同的来源。

对于已经以这种方式构建的现有网站,让多源 PWA 正常工作并非易事,但我们探索了一些潜在的解决方法。每种方法都各有利弊,因此请自行斟酌在网站上采用哪种方法。

在评估长期策略或网站重新设计时,请考虑迁移到单一源,除非有重要原因需要保留多源架构。

感谢各位在技术方面给出的意见和建议:Penny Mclachlan、Paul Covell、Dominick Ng、Alberto Medina、Pete LePage、Joe Medley、Cheney Tsai、Martin Schierle 和 Andre Bandarra。