如何构建多个 PWA,利用同一域名让用户知道它们属于同一组织或服务。
在“多源网站中的渐进式 Web 应用”博文中,Demian 讨论了基于多个来源构建的网站在尝试构建涵盖所有来源的单个渐进式 Web 应用时面临的挑战。
此类网站架构的一个示例是电子商务网站,其中:
- 首页位于
https://www.example.com
。 - 类别页面托管在
https://category.example.com
。 https://product.example.com
中的商品详情页。
如文章中所述,同源政策施加了多项限制,阻止跨源共享服务工作线程、缓存和权限。因此,我们强烈建议您避免使用此类配置,对于已采用这种方式构建网站的用户,请尽可能考虑迁移到单源网站架构。

在这篇博文中,我们将探讨相反的情况:不是在不同来源中使用单个 PWA,而是分析公司希望利用同一域名提供多个 PWA,并让用户知道这些 PWA 属于同一组织或服务的情况。
您可能已经注意到,我们使用了不同的相关术语,例如网域和来源。在继续之前,我们先回顾一下这些概念。
技术术语
- 网域:域名系统 (DNS) 中定义的任何标签序列。例如:
com
和example.com
是网域。 - 主机名:可解析为至少一个 IP 地址的 DNS 条目。例如:
www.example.com
是主机名,example.com
如果有 IP 地址,则可以是主机名,而com
永远不会解析为 IP 地址,因此永远不会是主机名。 - 来源:架构、主机名和(可选)端口的组合。例如,
https://www.example.com:443
是一个来源。
顾名思义,同源政策对来源施加了限制,因此在本文中,我们大多会使用“来源”一词。不过,我们有时会使用“网域”或“子网域”来描述所用的技术,以便创建不同的“来源”。
多个相关 PWA 的应用场景
在某些情况下,您可能希望构建独立的应用,但仍希望将它们标识为属于同一组织或“品牌”。重复使用相同的域名是建立这种关系的好方法。例如:
- 某电子商务网站想要创建一种独立体验,让卖家能够管理自己的商品目录,同时确保他们了解该体验属于用户购买商品的主网站。
- 一家体育新闻网站想要为某项重大体育赛事打造一款专用应用,让用户能够通过通知接收有关自己喜爱的赛事的统计信息,并将其作为渐进式 Web 应用进行安装,同时确保用户能够识别出该应用是由该新闻公司打造的。
- 某公司想要构建单独的聊天、邮件和日历应用,并希望这些应用作为与公司名称相关联的独立应用运行。

使用单独的来源
在上述情况下,建议每个概念上不同的应用都位于自己的来源中。
如果您想在所有这些网域中使用相同的域名,可以使用子网域来实现。例如,一家提供多款互联网应用或服务的公司可以将邮件应用托管在 https://mail.example.com
,将日历应用托管在 https://calendar.example.com
,同时在 https://www.example.com
提供其主要业务服务。另一个示例是某个体育网站想要创建一个完全专注于重要体育赛事(例如 https://footballcup.example.com
的足球锦标赛)的独立应用,用户可以安装该应用并独立于正文育网站(托管在 https://www.example.com
)使用该应用。此方法可能也适用于允许客户以公司品牌创建独立应用的平台。例如,一款可让商家在 https://merchant1.example.com
、https://merchant2.example.com
等处创建自己的 PWA 的应用。
使用不同的来源可确保应用之间相互隔离,这意味着每个应用都可以独立管理不同的浏览器功能,包括:
- 可安装性:每个应用都有自己的清单,并提供自己的可安装体验。
- 存储空间:每个应用都有自己的缓存、本地存储空间以及基本上所有形式的设备本地存储空间,而不会与其他应用共享这些空间。
- Service Worker:每个应用都有自己的 Service Worker,用于注册的范围。
- 权限:权限也按来源划分范围。这样一来,用户就能确切知道自己正在为哪个服务授予权限,并且通知等功能将正确归因于每个应用。
在多个独立的 PWA 的使用场景中,这种程度的隔离是最理想的,因此我们强烈建议采用这种方法。
如果子网域上的应用想要相互共享本地数据,仍然可以通过 Cookie 来实现。对于更高级的场景,可以考虑通过服务器同步存储空间。

使用相同的来源
第二种方法是在同一来源上构建不同的 PWA。这包括以下场景:
不重叠的路径
多个 PWA 或概念性“Web 应用”,托管在同一来源上,具有不重叠的路径。例如:
https://example.com/app1/
https://example.com/app2/
重叠/嵌套的路径
同一来源上的多个 PWA,其中一个的范围嵌套在另一个的范围内:
https://example.com/
(“外部应用”)https://example.com/app/
(“内部应用”)
借助 service worker API 和清单格式,您可以使用路径级范围来实现上述任一目标。不过,在这两种情况下,使用同一来源都会带来许多问题和限制,其根本原因在于浏览器不会将它们完全视为不同的“应用”,因此不建议采用这种方法。

在下一部分中,我们将更详细地分析这些挑战,以及在无法使用单独来源的情况下可以采取的措施。
多个同源 PWA 面临的挑战
以下是同源方法中常见的一些实际问题:
- 存储空间:Cookie、本地存储空间和所有形式的设备本地存储空间在应用之间共享。因此,如果用户决定清除某个应用的本地数据,系统会清除来源中的所有数据;无法仅针对单个应用执行此操作。请注意,Chrome 和某些其他浏览器会在卸载某个应用时主动提示用户清除本地数据,这也会影响来源中其他应用的数据。另一个问题是,应用还必须共享其存储空间配额,这意味着如果其中一个应用占用了过多的空间,另一个应用就会受到负面影响。
- 权限:浏览器权限与来源相关联。也就是说,如果用户向某个应用授予权限,该权限将同时应用于相应来源的所有应用。这可能听起来不错(不必多次请求权限),但请注意:如果用户禁止向某个应用授予权限,则会阻止其他应用请求该权限或使用相应功能。请注意,即使浏览器权限只需针对每个来源授予一次,系统级权限也必须针对每个应用授予一次,无论多个应用是否指向同一来源。
- 用户设置:设置也是按来源设置的。例如,如果两个应用的字体大小不同,而用户只想调整其中一个应用的缩放比例来弥补这一差异,那么他们将无法实现这一目标,除非同时将该设置应用于其他应用。
这些挑战使得我们难以鼓励采用这种方法。不过,如果您无法使用单独的来源(例如子网域),如使用单独的来源部分中所述,那么在所介绍的两个同源选项中,强烈建议使用不重叠的路径,而不是重叠/嵌套的路径。
如上所述,本部分讨论的挑战对于同源方法和跨源方法都是常见的。在下一部分中,我们将深入探讨为什么不建议使用重叠/嵌套路径策略。
重叠/嵌套路径的其他挑战
重叠/嵌套路径方法(其中 https://example.com/
是外部应用,https://example.com/app/
是内部应用)的另一个问题是,内部应用中的所有网址实际上都会被视为外部应用和内部应用的一部分。
但在实践中,这会带来以下问题:
- 安装推广:如果用户访问内部应用(例如,在网络浏览器中),而外部应用已安装在用户的设备中,则浏览器不会显示安装推广横幅,并且不会触发 BeforeInstallPrompt 事件。原因是浏览器会检查并查看当前网页是否属于已安装的应用,并得出肯定的结论。此问题的解决方法是手动安装内部应用(通过“创建快捷方式”浏览器菜单选项),或者先安装内部应用,然后再安装外部应用。
- 通知和标记 API:如果外部应用已安装,但内部应用未安装,则来自内部应用的通知和标记会错误地归因于外部应用(这是已安装应用的最近封闭范围)。如果两个应用都安装在用户的设备上,此功能可正常运行。
- 链接捕获:外部应用可能会捕获属于内部应用的网址。如果外部应用已安装但内部应用未安装,则尤其可能发生这种情况。同样,外部应用中指向内部应用的链接不会捕获到内部应用中,因为它们被视为在外部应用的范围内。此外,在 ChromeOS 和 Android 上,如果这些应用以可信 Web 活动的形式添加到 Play 商店,则外部应用将捕获所有链接。即使安装了内部应用,操作系统仍会向用户提供在外部应用中打开内部应用的选项。
总结
在本文中,我们介绍了开发者如何在同一网域中构建多个相互关联的渐进式 Web 应用。
总而言之,我们强烈建议您使用不同的来源(例如使用子网域)来托管独立的 PWA。将它们托管在同一来源中会带来许多挑战,主要是因为浏览器不会将它们完全视为不同的应用。
- 单独的来源:推荐
- 相同来源、不重叠的路径:不推荐
- 相同来源,路径重叠/嵌套:强烈不建议
如果无法使用不同的来源,强烈建议使用不重叠的路径(例如 https://example.com/app1/
和 https://example.com/app2/
),而不是使用重叠或嵌套的路径,例如 https://example.com/
(对于外部应用)和 https://example.com/app/
(对于内部应用)。
其他资源
衷心感谢以下人员提供的技术审核和建议:Joe Medley、Dominick Ng、Alan Cutter、Daniel Murphy、Penny McLachlan、Thomas Steiner 和 Darwin Huang
照片由 Tim Mossholder 拍摄,选自 Unsplash