摘要
在 2016 年节日季,“追踪圣诞老人”已迅速升级为一款离线渐进式 Web 应用, 这在一定程度上要归功于我们现有的场景设计。
结果
- Santa 是一款支持添加到主屏幕 (ATHS) 和离线功能的渐进式 Web 应用 (PWA)
- 10% 的符合条件的工作时段是通过 ATHS 图标开始的
- 75% 的用户原生支持自定义元素和 shadow DOM,而自定义元素和 shadow DOM 是网页组件的两个核心部分
- Lighthouse 的得分为 81
- 离线,通过 Service Worker API,与延迟加载绑定,仅缓存访问的场景,并在新版本中静默升级它们
背景
追踪圣诞老人是 Google 的一项节日传统。 每年 12 月,你都可以通过游戏和教育体验来欢度佳节。 在圣诞老人好好休息一会儿,小精灵们也在努力让“追踪圣诞老人” 无论是在网页上还是在用于实现 Android。
在网络上,“追踪圣诞老人”是一个大型的互动式网站,其中包含许多独特的“场景” - 这些场景的作者
Polymer - 支持大多数现代浏览器。
评估用户的浏览器是否“现代”通过特征检测来确定:
圣诞老人需要
Set
和
Web Performance API,例如
其他。
2016 年,我们升级了“追踪圣诞老人”的引擎,以支持大多数人的离线体验 场景。 这不包括由 YouTube 视频提供支持的场景或涉及圣诞老人实时位置的场景, 当然,只有在与北极直接连接的情况下才可用!📶☃️
<ph type="x-smartling-placeholder">
挑战
Santa 采用了自适应设计,能够在手机、平板电脑和桌面设备上顺畅运行。 该网站提供出色的多媒体内容,包括风格化视觉效果和节日主题音频,令人愉悦。 但是,常规版本的“追踪圣诞老人”为数百 MB! 原因如下:
- 圣诞老人支持超过 35 种语言,因此必须复制很多资源。
- 不同的平台有不同的媒体支持(例如,mp3 和 ogg)。
- 多媒体文件有时会有不同的大小和分辨率。
圣诞老人的小精灵们在整个 12 月也在努力工作,经常发布新的重要更新 。 这意味着用户浏览器已缓存的资源在重复访问时可能需要进行刷新。
这些挑战:
- 适用于不同“场景”的大型多媒体资源
- 整月发布的更改
...导致单纯的线下策略不适合。
使用 Polymer 构建而成的圣诞老人
在此之前,有必要回过头来谈谈圣诞老人的整体设计,然后再深入了解我们的 升级为离线 PWA。
Santa 是一款单页应用,最初是使用 Polymer 0.5 编写的,现在已升级为 Polymer 1.7. 圣诞老人由一组共享的代码组成:路由器、共享的导航资源等。 它还具有许多独特的“场景”。

可通过不同的网址访问每个场景:/village.html
、/codelab.html
和
/boatload.html
- 也是它自己的网络组件。
当用户打开场景时,我们会预加载所有必需的 HTML 和资源(图片、音频、css、js),
位于“追踪圣诞老人”代码库中的 /scenes/[[sceneName]]
下。
在此过程中,用户会看到一个友好的预加载器,其中会显示进度。
这种方法意味着我们不必为用户看不到的场景加载不必要的资源 (数据量很大)。 这也意味着我们需要保留一个内部“缓存清单”所有需要的资源 每个场景缓存清单是一个 JSON 文件,其中存储了从文件名到 MD5 哈希的映射 其内容的一部分。
加载你使用的内容
该模型节省了带宽,只提供用户访问场景所需的资源, 而不是一次预加载整个网站 “追踪圣诞老人”利用 Polymer 的功能,在运行时(而不是 加载时间。 请考虑下列代码段:
<lazy-pages id="lazypages" selected-item="{{selectedScene}}" ... >
<dorf-scene id="village" route="village" icon="1f384" permanent
mode$="[[mode]]"
path$="scenes/dorf/dorf-scene_[[language]].html"
class="santa-scene" allow-page-scrolling></dorf-scene>
<boatload-scene route="boatload" icon="26f5"
path$="scenes/boatload/boatload-scene_[[language]].html"
loading-bg-color="#8fd7f7"
loading-src="scenes/boatload/img/loading.svg"
logo="scenes/boatload/img/logo.svg"
class="santa-scene"></boatload-scene>
“追踪圣诞老人”按照以下步骤加载场景,例如boatload-scene
:
- 所有场景元素(包括
<boatload-scene>
)最初都未知,均被视为HTMLUnknownElement
,其中包含一些其他属性。 - 当所选场景发生更改时,系统会通知
<lazy-pages>
元素。 <lazy-pages>
元素会解析场景的元素和path
属性,从而加载 HTML 导入scenes/boatload/boatload-scene_en.html
。 此文件包含 Polymer 元素及其依赖元素。- 已显示友好的预加载器。
- 加载并执行 HTML 导入后,
<boatload-scene>
会透明地升级为 真正的聚合物元素,洋溢节日气氛。🎄🎉
这种方法有其挑战。例如,我们不希望包含重复的网络组件。
如果两个场景使用同一个元素,例如,paper-button
,我们在构建过程中将其剥离了
程序,改为将它添加到圣诞老人的共享代码中。
离线设计
借助 Polymer 和 lazy-pages
,“追踪圣诞老人”已经完美地细分为多个场景;加上每个
场景都有自己的目录。
我们设计了“追踪圣诞老人”的 Service Worker,它是支持离线运行在
用户的浏览器,要注意共享代码与“场景”区别。
Service Worker 背后的理论是什么?当用户使用支持的浏览器加载您的网站时,
前端 HTML 可以请求安装 Service Worker。
对于“追踪圣诞老人”,Service Worker 位于 /sw.js
。
这会触发一个 install
事件,该事件将预缓存圣诞老人的所有共享代码,因此不需要任何内容
。

安装 Service Worker 后,它就能够拦截所有 HTTP 请求。 对于“追踪圣诞老人”,经过简化的决策流程如下所示:
- 请求是否已缓存?
- 太棒了!返回缓存的响应。
- 请求是否与场景目录(例如“scenes/boardingload/SHIPload-scene_en.html”)相匹配?
- 执行网络请求,并将结果存储在缓存中,然后再将其返回给用户。
- 否则,请执行常规网络请求。
我们的流程和 install
事件允许“追踪圣诞老人”加载,即使用户处于离线状态也是如此。
但是,您只能看到用户之前加载的场景。
这非常适合于重玩比赛和打破您的最高得分。
敏锐的观察者可能会注意到,我们的缓存策略不允许对内容进行更改。 文件一旦缓存到用户的浏览器中,就绝不会被更改。 稍后会进一步介绍。
我们会直播
正如我们之前提到的,圣诞老人的小精灵们在整个 12 月份都在努力工作,他们经常要放假
最新更新。
构建“追踪圣诞老人”版本后,该版本将获得一个唯一标签,例如,v20161204112055
,即
发行时的时间戳(2016 年 12 月 4 日 11:20:55)。
对于这一带标签的版本,我们为每个文件生成 MD5 哈希,并将其存储在我们的 清单”。 在新型固态硬盘上,这只会使构建流程增加几秒钟。
每个版本都会部署到 Google 的静态缓存服务器上的唯一路径。 也就是说,旧版本绝不会移除。 也就是说,在新版本发布后,所有资源都将具有不同的网址,即使它们没有 而且由浏览器或 Service Worker 缓存的任何内容都毫无用处,除非 额外工作。
我们还部署了新版本的“prod”资源—圣诞老人索引 HTML 和服务 worker,位于 https://santatracker.google.com/ 上。 这会覆盖旧版本。

每当加载“追踪圣诞老人”时,浏览器都会检查是否有更新的 Service Worker,并提取它(如果
可用。
在我们的例子中,每个版本都会生成一个字节不同的代码。
浏览器将此视为升级,并执行新的 install
事件。
此时,用户的浏览器将查看新的“缓存清单”。 系统会将该 MD5 哈希与用户的现有缓存进行比较,如果资产具有不同的 MD5 哈希值, 将其从缓存中删除,然后请求浏览器重新获取。 但在大多数情况下,缓存的内容基本相同或只有细微的差别。

在“追踪圣诞老人”中,升级 Service Worker 会导致用户的浏览器立即重新加载。
离线浏览体验
当然,为了支持离线体验,我们还必须对界面进行一些更改, 使用户更轻松地理解网站。
系统会在您离线浏览时显示一个小横幅。 所有未缓存的场景都会“冻结”且无法点击 这样,用户便无法访问无法访问的内容。

“追踪圣诞老人”定期向圣诞老人的 API 发出请求。
如果这些请求失败或超时,我们会假定用户处于离线状态。
我们使用此 API,而不是浏览器的内置 navigator.onLine
property:这只会
通知我们该用户是否在线。(也称为 Lie-Fi)。
国际连接
我们的大部分用户使用的是英语(其次是日语、葡萄牙语、西班牙语和 法语版),圣诞老人是使用超过 35 种语言建造和发布的。
当用户加载“追踪圣诞老人”时,我们使用 浏览器的语言 以及选择投放语言的其他提示 大多数用户从未覆盖此语言。 但是,如果用户通过我们的选择器选择新语言,我们会将其视为升级 可用—正如上述情况一样,当有新版本的“追踪圣诞老人”推出时。

换句话说,Service Worker 就是“追踪圣诞老人”的当前版本。 实际上是 (build,language) 的元组。
添加到主屏幕
由于 Santa 会离线工作并提供 Service Worker,因此系统会提示符合条件的用户进行安装 主屏幕上 2016 年,大约 10% 的符合条件的加载来自主屏幕图标。
总结
我们能够快速将“追踪圣诞老人”转换为离线 PWA,从而实现了可靠且具有吸引力的 这得益于我们现有的场景设计,这得益于我们现有的 Polymer 和 Web 组件。 它还利用我们的构建系统执行高效的升级,仅使已更改的资源失效。
虽然“圣诞老人”很大程度上是定制的解决方案,但其许多原则都可以在 Polymer 中找到 项目的 App Toolbox。 如果您要从头开始构建新的 PWA,或者您正在寻找 与框架无关的方法,请尝试 Workbox 库。