Google_PWA_ServiceWork_渐进式 Web 应用_给应用提供离线体验

  前言:今天结识了google PWA提供的一个对移动端Web应用提供离线体验的一个功能,感受颇有用。我这里不分享本身的写法和代码。官网文档说的很详细,直接粘过来你们看吧。css

  推荐官网地址:你的第一个渐进式 Web 应用(Progressive Web App - PWA)html

 

 

文章详情以下:node

 

简介

什么是 Web 应用,一个渐进式 Web 应用?

渐进式 Web 应用会在桌面和移动设备上提供可安装的、仿应用的体验,可直接经过 Web 进行构建和交付。它们是快速、可靠的 Web 应用。最重要的是,它们是适用于任何浏览器的 Web 应用。若是你在构建一个 Web 应用,其实已经开始构建渐进式 Web 应用了。android

快速 & 可靠

每一个 Web 体验都必须快速,对于渐进式 Web 应用更是如此。快速是指在屏幕上获取有意义内容所需的时间,要在不到 5 秒的时间内提供交互式体验。ios

而且,它必须真的很快。很难形容可靠的高性能有多重要。能够这样想: 本机应用的首次加载使人沮丧。应用商店和漫长的下载都是门槛,不过,该前期成本包含了从此该应用的每次启动时间,一旦你安装好该应用,这些启动过程就不会再有额外的延迟。每一个应用的启动速度都与上一次同样快,没有任何差别。已安装的渐进式 Web 应用必须能让用户得到可靠的性能。git

安装

渐进式 Web 应用能够在浏览器选项卡中运行,但也能够安装。为网站添加书签只是添加了一个快捷方式,但已安装的渐进式 Web 应用的外观和行为会与任何已安装的应用相似。它与其它应用的启动位置是同样的。你能够控制启动体验,包括自定义启动画面、图标等。它在应用窗口中做为应用运行,没有地址栏或其它浏览器 UI。与其它已安装的应用同样,它是任务管理器中的顶级应用。github

请记住,让可安装的 PWA 保持快速可靠是相当重要的。安装 PWA 的用户但愿他们的应用正常运行,不管他们使用何种网络链接。这是每一个已安装应用必须知足的基线预期。web

手机 & 桌面

使用响应式设计技术,渐进式 Web 应用可在移动桌面上工做,使用跨平台的单一代码库。若是你正在考虑是否编写本机应用,请看看 PWA 提供的好处。chrome

你将构建什么

在此 codelab 中,你将使用渐进式 Web 应用技术构建天气 Web 应用。你的应用将:shell

  • 使用响应式设计,所以它可用在桌面或移动设备上。
  • 快速,使用 Service Worker 来预缓存运行时所需的应用资源(HTML,CSS,JavaScript,图像),并在运行时缓存天气数据以提升性能。
  • 可安装,使用 Web 应用清单(manifest)和 beforeinstallprompt 事件告诉用户它是可安装的。
  •  

    95fe6f7fbeee5bb1.png

Warning:为了简化此 codelab ,并解释提供离线体验的基础知识,咱们使用的是原生 JavaScript。在生产应用中,咱们强烈建议使用 Workbox 工具来构建 Service Worker 。它能够帮你消除许多可能遇到的坑和死角。

你将学到什么

  • 如何建立和添加 Web 应用清单
  • 如何提供简单的离线体验
  • 如何提供完整的离线体验
  • 如何使你的应用可安装

此 codelab 专一于渐进式 Web 应用。屏蔽了不相关的概念和代码块,并为你提供简单的复制和粘贴。

你须要什么

  • 最近版本的 Chrome(74 或更高版本)

    PWA 只是个 Web 应用,所以适用于全部浏览器,但咱们将使用 Chrome DevTools 的一些功能来更好地了解浏览器层面的状况,并用它来测试安装体验。

  • 了解 HTML,CSS,JavaScript 和Chrome DevTools 。

环境准备

获取 Dark Sky API 的密钥

咱们的天气数据来自 Dark Sky API。要使用它,你须要申请 API 密钥。它很容易使用,而且能够免费用于非商业项目。

注册一个 API Key

Note: 你还能够在没有 Dark Sky API 密钥的状况下完成此 codelab。若是咱们的服务器没法从 Dark Sky API 获取真实数据,它将返回假数据。

验证你的 API 密钥是否正常工做

要测试你的 API 密钥是否正常工做,请向 DarkSky API 发出 HTTP 请求。修改如下网址,将 DARKSKY_API_KEY 替换为你的 API 密钥。若是一切正常,你应该看到纽约市的最新天气预报。

https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319

获取代码

咱们已将此项目所需的一切都放入 Git 仓库中。首先,你须要获取代码并在你喜欢的开发环境中打开它。对于此代码库,咱们建议使用 Glitch。

强烈推荐: 使用 Glitch 导入仓库

推荐用 Glitch 来使用此代码库。

  1. 打开一个新的浏览器选项卡,而后转到 https://glitch.com 。
  2. 若是你没有账户,则须要注册。
  3. 单击 New Project,而后单击 Clone from Git Repo.
  4. 克隆 https://github.com/googlecodelabs/your-first-pwapp.git 并单击肯定。
  5. 获取完 repo 后,编辑 .env 文件,并使用 DarkSky API 密钥来更新它。
  6. 单击 Show Live 按钮以查看此 PWA 的运行状况。

替代方案: 下载代码并在本地工做

若是你想下载代码并在本地工做,你须要安装好最新版本的 Node 和代码编辑器。

Caution: 若是你在本地工做,某些 Lighthouse 审计可能没法经过,甚至可能没法安装,由于本地服务器并无在安全环境下运行。

下载源码

  1. 解压缩下载的 zip 文件。
  2. 运行 npm install 以安装运行服务器所需的依赖项。
  3. 编辑 server.js 并设置 DarkSky API 密钥。
  4. 运行 node server.js 以在端口 8000 上启动服务器.
  5. 打开浏览器选项卡并转到 http://localhost:8000

创建基线

咱们的起点是什么?

咱们的起点是为此 codelab 设计的基本天气应用。代码已经大幅简化,以突显此代码库中的概念,而且它几乎没有作错误处理。若是你选择在生产应用中复用此代码,请确保处理各类错误并彻底测试全部代码。

咱们将试着......

  1. 使用右下角的蓝色加号按钮来添加新城市。
  2. 使用右上角的刷新按钮来刷新数据。
  3. 使用每张城市卡片右上角的 x 来删除城市。
  4. 了解它在桌面和移动设备上的工做原理。
  5. 看看当离线时会发生什么。
  6. 使用 Chrome 的“网络”面板,查看当网络受限制为慢速3G 时会发生什么。
  7. 经过更改 server.js 中的 FORECAST_DELAY 为天气预报服务器添加延迟

用 Lighthouse 进行审计

Lighthouse是一款易于使用的工具,可帮助你提升网站和网页的质量。它可用来对性能,可访问性,渐进式 Web 应用等进行审计。每种审计及都有一个参考文档,解释了该审计为什么重要,以及如何解决所发现的问题。

 

 

咱们将使用 Lighthouse 来审计咱们的天气应用,并验证咱们所作的更改。

Note: 你能够在 Chrome DevTools 中,以命令行或 Node 模块的方式运行 Lighthouse。考虑将Lighthouse 添加到你的构建流程中,以确保你的 Web 应用不会出现回归问题。

让咱们运行 Lighthouse

  1. 在新选项卡中打开项目。
  2. 打开 Chrome DevTools 并切换到 Audits 选项卡,DevTools 会显示审计类别列表,所有启用它们。
  3. 单击 Run audits,60-90 秒后,Lighthouse 会在页面上显示报告。

审计渐进式 Web 应用

咱们将重点关注渐进式 Web 应用的审计结果。

af1a64a13725428e.png

这里有不少红色信息须要关注:

  • ❗失败: 在离线时当前页面未给出 200 响应。
  • ❗失败: 在离线时 start_url 未给出 200 响应。
  • ❗失败: 未注册用来控制页面和 start_url 的 Service Worker。
  • ❗失败: Web 应用清单 (manifest) 不符合可安装性要求。
  • ❗失败: 未配置自定义闪屏。
  • ❗失败: 未设置地址栏的主题颜色。

让咱们进入并开始修复其中的一些问题!

添加 Web 应用清单

到本节结束时,咱们的天气应用将经过如下审计:

  • Web 应用清单 (manifest) 不符合可安装性要求。
  • 未配置自定义的初始屏幕。
  • 未设置地址栏的主题颜色。

建立 Web 应用清单

Web 应用清单是一个简单的 JSON 文件,它使开发人员可以控制本应用对用户的显示方式。

使用 Web 应用清单,你的 Web 应用能够:

  • 告诉浏览器你但愿本应用在独立窗口中打开( display )。
  • 定义首次启动本应用时要打开哪一个页面( start_url )。
  • 定义应用在 Dock 或应用启动器上应该是什么样子( short_name ,icons )。
  • 建立一个闪屏( name , icons , colors )。
  • 告诉浏览器以横向或纵向模式打开窗口( orientation )。
  • 以及不少其它配置 。

在项目中建立名为 public/manifest.json 的文件,并复制/粘贴如下内容:

public/manifest.json

 
{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
    "src": "/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

本清单可支持一组用于不一样屏幕尺寸的图标。对于此 codelab ,咱们还包含了一些其它的,由于咱们须要把它们集成进 iOS。

Note: 若是想安装它,Chrome 会要求你至少提供 192x192px 图标和 512x512px 图标。可是你也能够提供其它尺寸。 Chrome 会使用最接近 48dp 的图标,例如,2x 设备上的 96px 或 3x 设备的 144px。

添加指向 Web 应用清单的连接

接下来,咱们须要经过向应用中的每一个页面添加 <link rel="manifest"... 来把此清单告知浏览器。把下列代码行添加到 index.html 文件中的 <head> 元素下。

public/index.html

 
<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">

DevTools Detour

DevTools 提供了一种快速简便的方法来检查你的 manifest.json 文件。打开 Application 面板上的 Manifest 窗格。若是你已正确添加清单信息,你将可以在此窗格中看到它以对人类友好的格式进行解析和显示。

 

添加 iOS 元标记和图标

iOS 上的 Safari 不支持 Web 应用清单( 至少目前为止 ),所以你须要将传统的 meta 标签添加到 index.html 文件的 <head> 中:

public/index.html

 
<!-- CODELAB: Add iOS meta tags and icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">

额外工做: 简易 Lighthouse 修复

咱们的 Lighthouse 审计还提出了一些其它很容易解决的问题,因此让咱们先来处理下这些问题。

设置元描述

根据 SEO 审计,Lighthouse 提出咱们的 “ Document does not have a meta description. ”。描述能够显示在 Google 的搜索结果中。高质量,独特的描述可使你的搜索结果与搜索用户更相关,并能够增长搜索流量。

要添加描述,请将如下 meta 标记添加到文档的 <head> 中:

public/index.html

 
<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">

设置地址栏的主题颜色

在 PWA 审计中,Lighthouse 提出咱们的应用“ Does not set an address-bar theme color ”。将浏览器的地址栏设置为与你的品牌色相匹配,能够提供更加沉浸式的用户体验。

要在移动设备上设置主题颜色,请将如下 meta 标记添加到文档的 <head> 中:

public/index.html

 
<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />

用 Lighthouse 验证更改

再次运行 Lighthouse(经过单击“审计”窗格左上角的+号)并验证你的更改。

SEO 审计

*✅经过: 文档已有元描述。

渐进式应用审计

  • ❗失败: 在离线时当前页面未给出 200 响应。
  • ❗失败: 在离线时 start_url 未给出 200 响应。
  • ❗失败: 未注册用来控制页面和 start_url 的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。

提供基本的离线体验

用户会期待所安装的应用在离线时始终具备基线体验。所以对于可安装的 Web 应用来讲,永远不会显示 Chrome 的离线恐龙图是相当重要的。离线体验的范围包括简单的离线页面、具备先前缓存数据的只读体验,甚至具备彻底功能的离线体验(在网络链接恢复时自动同步)。

在本节中,咱们将向天气应用添加一个简单的离线页面。若是用户在离线时尝试加载应用,则会显示咱们的自定义页面,而不是浏览器显示的典型离线页面。到本节结束时,咱们的天气应用将经过如下审计:

  • 在离线时当前页面未给出 200 响应。
  • 在离线时 start_url 未给出 200 响应。
  • 未注册用来控制页面和 start_url 的 Service Worker。

在下一部分中,咱们将使用完整的离线体验替换咱们的自定义离线页面。这将改善离线体验,但更重要的是,它将显著提升咱们的性能,由于咱们的大多数资产(HTML,CSS 和 JavaScript)都将在本地存储和提供,从而消除了网络方面的潜在瓶颈。

向 Service worker 求援

若是你对 Service Worker 不熟悉,能够经过阅读 Service Workers 简介来了解它们能够作什么,它们的生命周期如何工做等等,从而得到初步的理解。完成此 codelab 后,请务必查看 Debugging Service Workers code lab以便更深刻地了解如何与 Service Worker 合做。

经过 Service Worker 提供的功能应被视为渐进加强功能,而且仅在浏览器支持时才添加。例如,对于 Service Worker ,你能够为应用缓存应用外壳和数据,以便在网络不可用时也能使用它。若是不支持 Service Worker ,则不会调用离线代码,而用户将得到基本体验。使用特性检测来提供渐进加强功能的开销很小,而且在不支持该功能的旧浏览器中不会出错。

Warning: Service Worker 功能仅在经过 HTTPS 访问的页面上可用(http://localhost 及其等价物也可用来协助咱们进行测试)。

注册 Service Worker

第一步是注册 Service Worker 。将如下代码添加到 index.html 文件中:

public/index.html

 
// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

此代码检查 Service Worker API 是否可用,若是是,则在页面加载完毕时注册 /service-worker.js 的 Service Worker。

注意, Service Worker 是从根目录提供的,而不是从 /scripts/ 目录。这是设置 Service Worker 的 scope 的最简单方法。Service Worker 的 scope 会决定 Service Worker 能控制哪些文件,换句话说, Service Worker 将拦截哪一个路径下的请求。默认的 scope 是 Service Worker 文件所在的位置并及其各级目录。所以,若是 service-worker.js 位于根目录中,则 Service Worker 将控制对该域中全部网页的请求。

Precache 离线页面

首先,咱们须要告诉 Service Worker 缓存什么。咱们已经建立了一个简单的离线页面 ( public/offline.html ),只要没有网络链接,咱们就会显示它。

在 service-worker.js 中,将 '/offline.html', 添加到 FILES_TO_CACHE 数组中,最终结果应以下所示:

public/service-worker.js

 
// CODELAB: Update cache names any time any of the cached files change.
const FILES_TO_CACHE = [
  '/offline.html',
];

接下来,咱们须要修改 install 事件以告知 Service Worker 预先缓存离线页面:

public/service-worker.js

 
// CODELAB: Precache static resources here.
evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

Note: Service Worker 的事件和生命周期将在下一节中介绍。

咱们的 install 事件如今使用 caches.open() 打开缓存并传入缓存名称。提供缓存名称能让咱们对缓存资源进行版本控制或移除,这样咱们咱们就能轻松的修改一个而不影响另外一个。

一旦缓存打开,咱们就能够调用 cache.addAll() 了,它接受一个 URL 列表,从服务器获取这些 URL 并将其响应添加到缓存中。请注意,若是任何单个请求失败,cache.addAll() 就不会生效。这意味着你能够确保,若是安装步骤成功了,你的缓存必然处于一致的状态。可是,若是因为某种缘由而失败了,它将在下次 Service Worker 启动时自动重试。

DevTools Detour

让咱们来看看如何使用 DevTools 来理解和调试 Service Worker 。在从新加载页面以前,打开 DevTools,转到 Application面板上的 Service Workers 窗格。它应该以下所示:

 

当你看到这样的空白页面时,表示当前打开的页面中没有任何已注册的 Service Worker 。

如今,从新加载页面,“Service Worker” 窗格应以下所示:

69808e4bf3aee41b.png

当你看到这样的信息时,表示该页面正在运行 Service Worker 。

状态标签旁边有一个数字(这里是34251),在你使用 Service Worker 时,请密切注意该数字。这是一种判断你的 Service Worker 是否已更新的简单方式。

清理旧的离线页面

咱们将使用 activate 事件来清理缓存中的任何旧数据。此代码可确保你的 Service Worker 在任何应用外壳文件发生更改时更新其缓存。为了使其工做,你须要在 Service Worker 文件的顶部增长 CACHE_NAME 变量。

将如下代码添加到 activate 事件中:

public/service-worker.js

 
// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

DevTools Detour

打开 “Service Worker” 窗格,刷新页面,你将看到安装了新的 Service Worker ,而且状态编号会递增。

1db827d76bc0b359.png

更新后的 Service Worker 当即得到控制权,由于咱们的 install 事件以 self.skipWaiting() 结束, activate 事件以 self.clients.claim() 结束。若是没有这些,只要有一个打开着此页面的选项卡,旧的 Service Worker 就会继续控制此页面。

处理失败的网络请求

最后,咱们须要处理 fetch 事件。咱们将使用 "网络优先,回退到缓存" 的策略 。 Service Worker 将首先尝试从网络获取资源,若是失败,它将从缓存中返回离线页面。

6302ad4ba8460944.png

public/service-worker.js

 
// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
  // Not a page navigation, bail.
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

fetch 处理程序只须要处理页面导航,其它请求会被该处理程序忽略,交由浏览器进行常规处理。可是,若是该请求的 .mode 是 navigate ,就会尝试用 fetch 从网络获取项目。若是失败,则 catch 处理程序就会用 caches.open(CACHE_NAME) 打开缓存,并使用 cache.match('offline.html') 来得到预缓存的离线页面。而后使用 evt.respondWith() 将结果传回浏览器。

Key Point: 把 fetch 调用包装在 evt.respondWith() 中会阻止浏览器的默认处理,并告诉浏览器咱们要本身处理该响应。若是你没有在 fetch 处理程序中调用 evt.respondWith() ,你只会得到默认的网络行为。

DevTools Detour

让咱们检查一下,确保一切正常。打开 “Service Worker” 窗格,刷新页面,你将看到安装了新的 Service Worker ,而且状态编号会增长。

咱们还能够查看已缓存的内容。转到 DevTools 的 Application 面板上的 Cache Storage 窗格。右键单击 Cache Storage,选择 Refresh Caches,展开该部分,你应该会在左侧看到静态缓存的名称。单击缓存名称将显示缓存的全部文件。

c80a2a2e93c1c3ee.png

如今,咱们来测试下离线模式。返回 DevTools 的 Service Workers 窗格并检查 Offline 复选框。检查后,你应该会在 Network 面板选项卡旁边看到一个黄色警告图标。这表示你处于离线状态。

984b34dc2aa667a.png

从新加载你的页面......能够了!咱们获得咱们的离线熊猫图,而不是 Chrome 的离线恐龙图!

测试 Service Worker 的小技巧

当涉及缓存时,调试 Service Worker 多是一个挑战,若是缓存未按预期更新,事情可能变成噩梦。在典型的 Service Worker 生命周期和代码中的错误中,你可能会很快感到沮丧。可是,没必要如此。

使用 DevTools

在 “Application” 面板的“ Service Worker ”窗格中,有一些复选框可让你更轻松。

 

  • Offline - 选中后会模拟离线体验并阻止任何请求进入网络。
  • Update on reload - 选中后将得到最新的 Service Worker ,安装它,并当即激活它。
  • Bypass for network - 当检查请求时,绕过 Service Worker 并直接发送到网络。

来点新鲜的

在某些状况下,你可能会发现本身正在加载已缓存的数据或者没能按预期更新内容。要清除全部已保存的数据(localStorage,indexedDB 数据,缓存文件)并删除任何 Service Worker ,请使用 “Application” 选项卡中的 “Clear storage” 窗格。或者,你也可使用隐身窗口。

 

其它提示:

  • 当 Service Worker 取消注册以后,它可能仍然会列在这里,直到包含它的浏览器窗口关闭。
  • 若是你的应用打开了多个窗口,则直到将全部窗口都从新加载并更新到最新的 Service Worker 以前,新的 Service Worker 不会生效。
  • 取消注册 Service Worker 不会清除缓存!
  • 若是存在 Service Worker 而且注册了新的 Service Worker ,则除非你当即得到控制权 ,不然在从新加载页面以前,新的 Service Worker 将不会得到控制权。

使用 Lighthouse 验证这些更改

再次运行 Lighthouse 并验证你的更改。在验证更改以前,请不要忘记取消 “Offline” 复选框!

SEO 审计

*✅经过: 文档已有元描述。

渐进式应用审计

*✅经过: 在离线时当前页面未给出 200 响应。 *✅经过: 在离线时 start_url 未给出 200 响应。 *✅经过: 未注册用来控制页面和 start_url 的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。

提供完整的离线体验

将手机开启飞行模式,而后尝试运行一些你喜欢的应用。几乎在全部状况下,它们都提供了至关强大的离线体验。用户但愿他们的应用具备稳健的体验,Web 应用也不例外。渐进式 Web 应用在设计时应该把离线做为核心场景。

Key Point:离线优先的设计能够经过减小应用发出的网络请求数量并改用预先缓存资源并直接从本地缓存提供资源来大幅提升Web 应用的性能。即便用最快的网络链接,从本地缓存提供的服务也仍然会更快!

Service Worker 生命周期

Service Worker 的生命周期是最复杂的部分。若是你不知道它想要作什么以及有什么好处,你可能会以为它到处和你做对。可是一旦你知道它是如何工做的,你就能够为用户提供无缝、免打扰的更新,将网络应用和本机应用中最好的一面结合起来。

Key Point:此 codelab 仅涵盖 Service Worker 生命周期的基础知识。要深刻了解,请参阅有关 WebFundamentals 的 Service Worker 生命周期 文档。

install 事件

Service Worker 得到的第一个事件是 install 。它会在 Service Worker 执行时当即触发,而且每一个 Service Worker 只会调用一次。若是你更改了 Service Worker 脚本,浏览器就会将其视为另外一个 Service Worker,而且它将得到本身的 install 事件。

 

一般, install 事件用于缓存应用运行时所需的所有内容。

activate 事件

Service Worker 每次启动时都会收到 activate 事件。 activate 事件的主要目的是配置 Service Worker 的行为,清除之前运行中遗留的任何资源(例如旧缓存),并让 Service Worker 准备好处理网络请求(例以下面要讲的 fetch 事件)。

fetch 事件

fetch 事件容许 Service Worker 拦截并处理任何网络请求。它能够经过网络获取资源、能够从本身的缓存中提取资源、生成自定义响应,以及不少种不一样的选择。查看离线宝典了解你可使用的不一样策略。

更新 Service Worker

浏览器会检查每一个页面加载时是否有新版本的 Service Worker 。若是找到新版本,则会下载这个新版本并在后台安装,但不会激活它。它会处于等待状态,直到再也不打开任何使用旧 Service Worker 的页面。一旦关闭了使用旧 Service Worker 的全部窗口,新的 Service Worker 就会被激活并得到控制权。更多详细信息,请参阅 Service Worker 生命周期文档中的更新 Service Worker部分。

选择正确的缓存策略

选择正确的缓存策略取决于你尝试缓存的资源类型以及之后可能须要的资源。对于这个天气应用,须要缓存的资源能够分为两类: 须要预先缓存的资源以及将在运行时缓存的数据。

缓存静态资源

预先缓存资源与用户安装桌面或移动应用时的状况相似。应用运行所需的关键资源已安装或缓存在设备上,之后不管是否有网络链接均可以加载它们。

对于这个应用,咱们将在安装 Service Worker 时预先缓存全部静态资源,以便把咱们运行应用所需的一切都存储在用户的设备上。为了确保咱们的应用快速加载,咱们将使用缓存优先策略:不去网络获取资源,而是从本地缓存中取出;只有当缓存不可用时,咱们才会尝试从网络中获取它。

44860840e2090bd8.png

从本地缓存中取可消除任何网络方面的变数。不管用户使用何种网络(WiFi,5G,3G 甚至 2G),咱们须要运行的关键资源都几乎能够当即使用。

Caution: 在此示例中,使用 cache-first 策略提供静态资源,这会致使在不询问网络的状况下返回任何缓存内容的副本。虽然 cache-first 策略易于实施,但它可能会在未来的演化中带来挑战。

缓存应用数据

stale-while-revalidate strategy 对某些类型的数据是理想的,好比本应用。它会尽量快地获取屏幕上要显示的数据,而后在网络返回最新数据后进行更新。 Stale-while-revalidate 意味着咱们须要发起两个异步请求,一个到缓存,一个到网络。

6ebb2681eb1f58cb.png

在正常状况下,缓存数据几乎会当即返回,为应用提供可使用的最新数据。而后,当网络请求返回时,将使用来自网络的最新数据更新应用。

对于咱们的应用,这提供了比 "网络优先,回退到缓存" 策略更好的体验,由于用户没必要等到网络请求超时后才在屏幕上看到某些内容。他们最初可能会看到较旧的数据,但一旦网络请求返回,应用就将使用最新数据进行更新。

更新应用逻辑

如前所述,应用须要启动两个异步请求,一个到缓存,一个到网络。该应用使用 window 上的 caches 对象来访问缓存并获取最新数据。这是渐进加强的一个很好的例子,由于 caches 对象可能并不是在全部浏览器中均可用,若是不可用,网络请求仍然能够工做。

更新 getForecastFromCache() 函数,检查 caches 对象是否在全局 window 对象中可用,若是是,请从缓存中请求数据。

public/scripts/app.js

 
// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
  return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) {
        return response.json();
      }
      return null;
    })
    .catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });

而后,咱们须要修改 updateData()以便它进行两次调用,一次调用 getForecastFromNetwork() 以从网络获取天气预报,并发起另外一次 getForecastFromCache() 以获取缓存的最新天气预报:

public/scripts/app.js

 
// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });

咱们的天气应用如今发出两个异步数据请求,一个来自缓存,另外一个来自 fetch 。若是缓存中有数据,它将被很是快速地返回和渲染(几十毫秒)。而后,当 fetch 响应时,将使用直接来自天气 API 的最新数据更新卡片。

请注意缓存请求和 fetch 请求如何结束于更新天气预报卡片的调用。应用要如何知道它是否显示了最新的数据?这在 renderForecast() 的以下代码中处理:

public/scripts/app.js

 
// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
  return;
}

每次更新卡片时,应用都会将数据的时间戳存储在卡片上的隐藏属性中。若是卡片上已存在的时间戳比传递给函数的数据新,应用就什么也不作。

预先缓存咱们的应用资源

在 Service Worker 中,让咱们添加一个 DATA_CACHE_NAME 以便咱们能够将应用数据与应用外壳分开。更新应用外壳并清除旧缓存后,咱们的数据将保持不变,仍用于超快速加载。请记住,若是你的数据格式未来发生了变化,你就须要一种方法来处理这种状况,并确保应用外壳和内容保持同步。

public/service-worker.js

 
// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';

别忘了也要同时更新 CACHE_NAME,咱们还将更改全部的静态资源。

为了让本应用离线工做,咱们须要预先缓存它所需的全部资源。这也有助于提高性能。该应用无需从网络获取全部资源,而是能够从本地缓存加载全部资源,从而消除任何网络不稳定性。

把 FILES_TO_CACHE 数组改成以下文件列表:

public/service-worker.js

 
// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  '/images/cloudy.svg',
  '/images/fog.svg',
  '/images/hail.svg',
  '/images/install.svg',
  '/images/partly-cloudy-day.svg',
  '/images/partly-cloudy-night.svg',
  '/images/rain.svg',
  '/images/refresh.svg',
  '/images/sleet.svg',
  '/images/snow.svg',
  '/images/thunderstorm.svg',
  '/images/tornado.svg',
  '/images/wind.svg',
];

因为咱们在手动生成要缓存的文件列表,所以每当更新文件时也必须更新 CACHE_NAME。咱们能够从缓存文件列表中删除 offline.html,由于本应用如今具备离线工做所需的全部必要资源,不会再显示离线页面。

Caution:在此示例中,咱们手动控制着本身的 Service Worker。每次咱们更新任何静态资源时,咱们都须要从新刷新 Service Worker 并更新缓存,不然将提供旧内容。此外,当一个文件更改时,整个缓存都会无效并须要从新下载。这意味着即便修复一个简单的单字符拼写错误也将使缓存无效并要求再次下载全部内容 - 效率不够高。 Workbox 能优雅地处理它,经过将其集成到你的构建过程当中,只有已更改的文件才须要更新,为用户节省了带宽并让你更轻松地进行维护!

更新 activate 事件处理程序

为了防止咱们 activate 事件不当心删除数据,在 service-worker.js 的 activate 事件,把 if (key !== CACHE_NAME) { 改成:

public / service-worker.js

 
if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {

更新 fetch 事件处理程序

咱们须要修改 Service Worker 以拦截对 weather API 的请求并将其响应存储在缓存中,以便咱们之后能够轻松地访问它们。在 stale-while-revalidate 策略中,咱们但愿把网络响应做为“真相之源”,始终由它向咱们提供最新信息。若是不能,也能够失败,由于咱们已经在应用中检索到了最新的缓存数据。

更新 fetch 事件处理程序以便和其它对数据 API 的请求分开。

public/service-worker.js

 
// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
            .then((response) => {
              // If the response was good, clone it and store it in the cache.
              if (response.status === 200) {
                cache.put(evt.request.url, response.clone());
              }
              return response;
            }).catch((err) => {
              // Network request failed, try to get it from the cache.
              return cache.match(evt.request);
            });
      }));
  return;
}
evt.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(evt.request)
          .then((response) => {
            return response || fetch(evt.request);
          });
    })
);

该代码拦截请求并检查它是否用于天气预报。若是是,请使用 fetch 发出请求。一旦返回了响应,就打开缓存,克隆响应,将其存储在缓存中,而后将响应返回给原始请求者。

咱们须要删除 evt.request.mode !== 'navigate' 检查,由于咱们但愿这个 Service Worker 处理全部请求(包括图像,脚本,CSS 文件等),而不只仅是导航。若是咱们留着这个检查,则只会从 Service Worker 缓存中提供 HTML,其它全部内容都将从网络请求。

试一试

该应用如今应该能彻底离线工做。刷新页面以确保你已安装最新的 Service Worker ,而后保存几个城市并按应用上的刷新按钮以获取新的天气数据。

而后转到 DevTools 的 Application 面板上的 Cache Storage 窗格。展开该部分,你应该会在左侧看到静态缓存和数据缓存的名称。打开数据缓存会显示为每一个城市存储的数据。

731e91776cb6ef18.png

而后,打开 DevTools 并切换到 Service Workers 窗格,选中 Offline 复选框,而后尝试从新加载页面,而后离线并从新加载页面。

若是你是一个快速的网络上,并但愿看看天气预报数据会如何在慢速链接上更新,请在 server.js 中把 FORECAST_DELAY 设置为 5000 。这样对天气预报 API 的全部请求都将延迟 5000 毫秒。

用 Lighthouse 验证更改

再次运行 Lighthouse 是个好主意。

SEO 审计

*✅经过: 文档已有元描述。

渐进式应用审计

*✅经过: 在离线时当前页面未给出 200 响应。 *✅经过: 在离线时 start_url 未给出 200 响应。 *✅经过: 未注册用来控制页面和 start_url 的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。

添加安装体验

安装渐进式 Web 应用后,其外观和行为会与全部其它已安装的应用相似。它与其它应用启动时的位置相同。它在没有地址栏或其它浏览器 UI 的应用中运行。与全部其它已安装的应用同样,它是任务切换器中的顶级应用。

d824e1712e46a1cc.png

在 Chrome 中,能够经过其 "三点菜单" 来安装渐进式 Web 应用,也能够向用户提供按钮或其它 UI 组件,以提示他们安装你的应用。

Success: 因为 Chrome 的 "三点菜单" 的安装体验不够显眼,咱们建议你在应用中提供一些指示以通知用户你的应用能够安装,并使用安装按钮来完成安装过程。

用 Lighthouse 进行审计

为了使用户可以安装渐进式 Web 应用,它须要知足一些条件 。最简单的方法是使用 Lighthouse 来确保它符合可安装的标准。

b921f5583fcddf03.png

若是你使用此 codelab,你的 PWA 就已经符合这些标准了。

Key Point:对于本节,在 DevTools 的 Application 面板的 Service Workers 窗格中启用 Bypass for network 复选框。选中后,请求将绕过 Service Worker 并直接发送到网络。这简化了咱们的开发过程,由于咱们在完成本节中的任务时没必要更新咱们的 Service Worker 。

将 install.js 添加到 index.html

首先,让咱们将 install.js 添加到 index.html 文件中。

public/index.html

 
<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>

监听 beforeinstallprompt 事件

若是符合添加到主屏幕条件 ,Chrome 将触发 beforeinstallprompt 事件,你可使用该事件指示你的应用能够“安装”,而后提示用户安装它。添加以下代码以监听 beforeinstallprompt 事件:

public/scripts/install.js

 
// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

保存事件并显示安装按钮

在咱们的 saveBeforeInstallPromptEvent 函数中,咱们将保存对 beforeinstallprompt 事件的引用,以便咱们稍后能够调用它的 prompt() ,并修改咱们的 UI 以显示安装按钮。

public/scripts/install.js

 
// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

显示 "提示/隐藏" 按钮

当用户单击安装按钮时,咱们须要调用保存的 beforeinstallprompt 事件的 .prompt() 函数。咱们还须要隐藏安装按钮,由于 .prompt() 只能在每一个保存的事件上调用一次。

public/scripts/install.js

 
// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);

调用 .prompt() 将向用户显示模态对话框,请他们将你的应用添加到主屏幕。

记录结果

你能够经过监听所保存的 beforeinstallprompt 事件的 userChoice 属性返回的 Promise 来检查用户是如何响应的安装对话框。在显示出提示而且用户已对其做出响应后,Promise 将返回一个具备 outcome 属性的对象。

public/scripts/install.js

 
// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt', choice);
      } else {
        console.log('User dismissed the A2HS prompt', choice);
      }
      deferredInstallPrompt = null;
    });

对 userChoice 的一个说明, 规范中把它定义成了属性 ,而不是你所指望的函数。

记录全部安装事件

除了你所添加的用于安装应用的任何 UI 以外,用户还能够经过其它方法安装 PWA,例如 Chrome 的 "三点菜单"。要跟踪这些事件,请监听 appinstalled 事件。

public/scripts/install.js

 
// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);

而后,咱们须要修改 logAppInstalled 函数,对于这个 codelab,咱们只用了 console.log ,但在生产应用中,你可能但愿将其做为事件记录在你的分析软件中。

public/scripts/install.js

 
// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);

更新 Service Worker

不要忘记修改 service-worker.js 文件中的 CACHE_NAME,由于你对已缓存的文件作了些更改。在 DevTools 的 “Application” 面板上的 “Service Worker” 窗格中启用 Bypass for network 复选框只在开发中有用,在现实世界中是无济于事的。

试一试

让咱们看看咱们的安装步骤是如何进行的。为了安全起见,使用 DevTools 应用面板中的 Clear site storage 按钮清除全部内容以确保咱们从头开始。若是你以前安装过该应用,请务必将其卸载,不然安装图标将不会再次显示。

验证安装按钮是否可见

首先,验证咱们的安装图标是否正确显示了,请务必同时在桌面和移动设备上试用。

  1. 在新的 Chrome 标签页中打开网址。
  2. 打开 Chrome 的 "三点菜单"(地址栏旁边)。 ▢验证你在菜单中是否看到了 “Install Weather...”。
  3. 使用右上角的刷新按钮刷新天气数据,以确保咱们符合user engagement heuristics 。 ▢确认应用标题中显示了安装图标。

验证安装按钮是否有效

接下来,让咱们确保一切都安装正确,并正确触发了咱们的事件。你能够在桌面设备或移动设备上执行此操做。若是你想在移动设备上进行测试,请确保使用远程调试,以便查看控制台中的日志。

  1. 打开 Chrome,而后在新的浏览器标签中,导航到你的 Weather PWA。
  2. 打开 DevTools 并切换到 “Console” 窗格。
  3. 单击右上角的安装按钮。 ▢确认安装按钮消失了 ▢确认显示了安装模态对话框。
  4. 单击 “Cancel”。 ▢确认 “User dismissed the A2HS prompt” 显示在控制台输出中。 ▢确认从新出现了安装按钮。
  5. 再次单击安装按钮,而后单击模态对话框中的安装按钮。 ▢确认 “User accepted the A2HS prompt” 显示在了控制台输出中。 ▢确认 “Weather App was installed” 显示在了控制台输出中。 ▢确认天气应用已添加到你查找应用的典型位置。
  6. 启动 Weather PWA。 ▢确认应用是做为独立应用打开的,能够在桌面上的应用窗口中,也能够在移动设备上全屏显示。

请注意,若是你从 localhost 在桌面上运行,则你安装的 PWA 可能会显示地址标题,由于 localhost 不认为是安全主机。

验证 iOS 安装是否正常

咱们还要检查它在 iOS 上的行为。若是你有 iOS 设备,能够直接使用它;或者若是你使用的是 Mac,能够尝试使用 Xcode 提供的 iOS 模拟器。

  1. 打开 Safari,在新的浏览器选项卡中,导航到 Weather PWA。
  2. 单击 Share 8ac92dd483c689d3.png按钮。
  3. 向右滚动并单击 Add to Home Screen 按钮。 ▢验证标题、URL 和图标是否正确。
  4. 单击 Add。 ▢确认应用图标已添加到了主屏幕。
  5. 从主屏幕启动 Weather PWA。 ▢确认应用全屏启动。

额外工做: 检测你的应用是否从主屏幕启动了

媒体查询 display-mode 能够根据应用的启动方式来应用样式,或者使用 JavaScript 来断定它是如何启动的。

 
@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

你还能够在 JavaScript 中检测它是否运行在独立模式下来检查这个 display-mode 媒体查询 。

额外工做: 卸载你的 PWA

请记住,若是已经安装了应用,则 beforeinstallevent 不会触发,所以在开发期间,你可能须要屡次安装和卸载应用,以确保一切正常运行。

Android

在 Android 上,卸载 PWA 的方式与卸载其它已安装的应用的方式相同。

  • 打开应用抽屉。
  • 向下滚动以查找天气图标。
  • 将应用图标拖到屏幕顶部。
  • 选择卸载

ChromeOS

在 ChromeOS 上,能够从启动器搜索框轻松卸载 PWA。

  • 打开启动器。
  • 在搜索框中输入“Weather”,你的 Weather PWA 应该出如今搜索结果中。
  • 右键单击(按住 alt 键单击)Weather PWA。
  • 点击 从 Chrome 中删除...

macOS 和 Windows

在 Mac 和 Windows 上,必须经过 Chrome 卸载 PWA。

  • 在新的浏览器标签中,打开 chrome://apps。
  • 右键单击(按住 alt 键单击)Weather PWA。
  • 点击从 Chrome 中删除...

恭喜

恭喜,你已经成功构建了第一个渐进式 Web 应用!

你添加了一个 Web 应用清单以使其可以安装,并添加了一个 Service Worker 以确保你的 PWA 始终快速并且可靠。你学习了如何使用 DevTools 审计应用以及如何用它帮你改善用户体验。

你如今知道了将任何 Web 应用转换为渐进式 Web 应用所需的关键步骤。

相关文章
相关标签/搜索