以 PWA 形式呈现“追踪圣诞老人”

查看网站

摘要

“追踪圣诞老人”在 2016 年节日季迅速升级为离线渐进式 Web 应用,这在一定程度上要归功于我们现有的场景设计。

成果

  • 圣诞老人是一款支持添加到主屏幕 (ATHS) 和离线功能的渐进式 Web 应用 (PWA)
  • 10% 的符合条件的会话都是通过 ATHS 图标开始的
  • 75% 的用户原生支持自定义元素和 shadow DOM(Web 组件的两个核心部分)
  • Lighthouse 得分为 81
  • 通过 Service Worker API,离线功能与延迟加载相关联,仅缓存访问过的场景,并在新版本中静默升级这些场景

背景

“追踪圣诞老人”是 Google 的一项节日传统。 每年 12 月,您都可以通过游戏和教育体验来庆祝这个节日。 尽管圣诞老人该休息了,但精灵们却在网页Android 上以开源方式发布“追踪圣诞老人”。

在网络上,“追踪圣诞老人”是一个大型的互动式网站,其中包含许多使用 Polymer 编写的独特“场景”,可支持大多数现代浏览器。 通过功能检测来确定用户的浏览器是否为“现代”浏览器:Santa 需要 SetWeb Performance API 等。

2016 年,我们升级了“追踪圣诞老人”背后的引擎,以支持大多数场景的离线体验。 这不包括以 YouTube 视频为后盾的场景或涉及圣诞老人的实时位置的场景,当然,这些场景只有在与北极直接相连时才可用!📶☃️

Android 设备上的“追踪圣诞老人”
Android 设备上的“追踪圣诞老人”

挑战

圣诞老人采用自适应设计,能够在手机、平板电脑和桌面设备上顺畅运行。 该网站提供出色的多媒体内容,包括风格化的视觉效果和以节日为主题的音频,令人感到愉悦。 但是,“追踪圣诞老人”的常规版本是几百兆字节! 原因如下:

  • 圣诞老人支持超过 35 种语言,因此许多资源必须复制。
  • 不同的平台具有不同的媒体支持(例如,mp3 和 ogg)。
  • 有时提供的多媒体文件的大小和分辨率各不相同。

圣诞老人的小精灵们在整个 12 月也都在努力工作,在整个月中经常发布新的重要更新。 这意味着,在重复访问时,可能需要刷新用户浏览器已缓存的资源。

这些挑战包括:

  • 适合不同场景的大型多媒体资源
  • 在整个月份内发布的更改

...会导致单纯的线下策略不适合。

用 Polymer 打造的圣诞老人

不妨先回过头去讨论圣诞老人的整体设计,然后再深入了解我们如何将其升级为离线 PWA。

Santa 是一款单页应用,最初使用 Polymer 0.5 编写,现在升级到 Polymer 1.7。圣诞老人由一组共享代码(路由器、共享导航资产等)组成。它还有许多独特的“场景”。

预加载器

每个场景均可通过不同的网址(/village.html/codelab.html/boatload.html)访问,并且每个场景都有自己的 Web 组件。当用户打开一个场景时,我们会预加载其所需的所有 HTML 和资源(图片、音频、css、js),它们位于“追踪圣诞老人”代码库中的 /scenes/[[sceneName]] 下。在此过程中,用户会看到显示进度的易用预加载器。

这种方法意味着我们不必为用户看不到的场景加载不必要的资源(数据量很大)。这也意味着我们需要为每个场景所需的所有资源保留一个内部“缓存清单”。缓存清单是一个 JSON 文件,其中存储了从文件名到其内容的 MD5 哈希的映射。

加载您使用的内容

此模型可节省带宽,仅提供用户访问场景所需的资源,而不是一次性预加载整个网站。 “追踪圣诞老人”利用 Polymer 的功能在运行时(而不是在加载时)升级自定义元素。 请考虑下列代码段:

<lazy-pages id="lazypages" selected-item="&#123;{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:

  1. 所有场景元素(包括 <boatload-scene>)最初都是未知的,并且全部被视为 HTMLUnknownElement 且具有一些额外的属性。
  2. 当所选场景发生更改时,<lazy-pages> 元素会收到通知。
  3. <lazy-pages> 元素会解析场景的元素和 path 属性,从而加载 HTML 导入 scenes/boatload/boatload-scene_en.html。其中包含 Polymer 元素及其依赖元素。
  4. 系统会显示友好的预加载器。
  5. 加载并执行 HTML 导入后,<boatload-scene> 会透明地升级为真正的 Polymer 元素,节日气氛满满。🎄🎉

这种方法存在一些挑战。例如,我们不希望包含重复的 Web 组件。如果两个场景使用同一个元素,例如,paper-button,我们在构建流程中会将其删除,改为将其包含在圣诞老人的共享代码中。

离线设计

借助 Polymer 和 lazy-pages,“追踪圣诞老人”已经被巧妙地细分为多个场景;此外,每个场景都有自己的目录。我们设计了“追踪圣诞老人”的 Service Worker,它是在用户浏览器上运行的离线功能,可以感知共享代码和“场景”的区别。

Service Worker 背后的理论是什么?当受支持的浏览器上的用户加载您的网站时,前端 HTML 可以请求安装 Service Worker。对于“追踪圣诞老人”,Service Worker 位于 /sw.js。这会触发 install 事件,该事件将预缓存圣诞老人的所有共享代码,因此在运行时不需要提取任何内容。

软件流程图

Service Worker 在安装后能够拦截所有 HTTP 请求。对于“追踪圣诞老人”,简化的决策流程如下所示:

  1. 请求是否已缓存?
    • 太好了!返回缓存的响应。
  2. 请求是否与场景目录(例如“scenes/买加载/船加载-scene_en.html”)匹配?
    • 执行网络请求,并将结果存储在缓存中,然后再将其返回给用户。
  3. 否则,请执行常规网络请求。

我们的流程和 install 事件允许“追踪圣诞老人”进行加载,即使用户处于离线状态也是如此。 不过,只有用户之前加载的场景可用。这非常适合重玩游戏并刷新最高得分。

敏锐观察者可能会注意到,我们的缓存策略不允许对内容进行更改。文件在用户的浏览器中缓存之后,就永远不会更改。 稍后会进一步介绍。

我们将实时进行

正如我们之前提到的,圣诞老人的小精灵们在整个 12 月都在努力工作,而且他们经常需要在一个月内发布新的更新。 “追踪圣诞老人”的版本制作完成后,系统会为其附加一个唯一标签,例如:v20161204112055,发布版本的时间戳(2016 年 12 月 4 日 11:20:55)。

对于此加标签的版本,我们会为每个文件生成 MD5 哈希,并将其存储在“缓存清单”中。在新型固态硬盘上,这只会增加构建流程几秒钟的时间。

每个版本都会部署到 Google 静态缓存服务器上的唯一路径。也就是说,较低的版本永远不会被移除。 这意味着,在新版本之后,所有资源都将具有不同的网址(即使资源未更改也是如此),并且浏览器或 Service Worker 缓存的任何内容都将变得无用,除非我们执行一些额外的操作。

我们还部署了新版资源(Santa 的索引 HTML 和 Service Worker),这些资源位于 https://santatracker.google.com/ 上。这会覆盖旧版本。

静态图表

每次加载“追踪圣诞老人”时,浏览器都会检查是否有更新的 Service Worker,并提取它(如果有)。在我们的示例中,每个版本都会生成一个字节不同的代码。浏览器将此视为升级,并执行新的 install 事件。

此时,用户的浏览器会检查新的“缓存清单”。系统会将其与用户的现有缓存进行比较,如果资源具有不同的 MD5 哈希,我们会从缓存中删除它们,并要求浏览器重新获取它。不过,在大多数情况下,缓存的内容大致相同或只有细微的差异。

缓存示意图

在“追踪圣诞老人”中,升级 Service Worker 会导致用户的浏览器立即重新加载。

离线浏览体验

当然,我们还必须对界面进行一些更改,以支持离线体验,同时也让那些可能不预料到网站可以离线工作的用户更容易理解。

当您进行离线浏览时,会看到一个小横幅。 所有未缓存的场景都会“冻结”且无法点击。这样,用户就无法访问不可用的内容。

线下

“追踪圣诞老人”会定期向圣诞老人的 API 发出请求。 如果这些请求失败或超时,系统会假定用户处于离线状态。 我们会使用此 API,而不是浏览器的内置 navigator.onLine 属性:该属性只会通知我们用户是否在线。(也称为 Lie-Fi)。

国际连接

虽然我们的大多数用户使用的是英语(其次是日语、葡萄牙语、西班牙语和法语),但圣诞老人是以超过 35 种不同语言构建和发布的。

当用户加载“追踪圣诞老人”时,我们会根据浏览器的语言和其他提示来选择要使用的语言。大多数用户永远不会覆盖此语言。 但是,如果用户通过选择器选择了新的语言,那么我们会将此视为可用升级,就像在上述示例中,当有新版本的“追踪圣诞老人”可用时一样。

语言

换句话说,目前用于 Service Worker 的“追踪圣诞老人”版本实际上是 (build,language) 的元组。

添加到主屏幕

由于圣诞老人在离线状态下工作并提供了 Service Worker,因此系统会提示符合条件的用户将其安装到主屏幕。在 2016 年,大约 10% 的符合条件的加载来自主屏幕图标。

总结

我们能够快速将“追踪圣诞老人”转换为离线 PWA,实现了可靠且引人入胜的体验。这要归功于我们现有的场景设计,而现在我们使用 Polymer 和 Web 组件简化了这些场景设计。它还利用我们的构建系统来执行高效的升级,仅让已更改的资产失效。

虽然 Santa 在很大程度上是一个定制的解决方案,但其许多原则都可以在 Polymer 项目的应用工具箱中找到。如果您是从头开始构建新的 PWA,我们建议您查看一下。或者,如果您要寻找一种与框架无关的方法,不妨试试 Workbox 库