开发渐进式 Web 应用 02.0:离线快速入门

此 Codelab 是由 Google Developers 培训团队开发的“渐进式 Web 应用开发”培训课程的一部分。如果您按顺序学习本 Codelab,将收获最多的课程价值。

如需了解本课程的完整详情,请参阅开发渐进式 Web 应用概览

简介

在本实验中,您将使用 Lighthouse 审核网站是否符合渐进式 Web 应用 (PWA) 标准。此外,您还可以使用 Service Worker API 添加离线功能。

学习内容

  • 如何使用 Lighthouse 审核网站
  • 如何为应用添加离线功能

注意事项

  • 基本 HTML、CSS 和 JavaScript
  • 熟悉 ES2015 Promise

所需条件

  • 可连接终端/shell 的计算机
  • 互联网连接
  • Chrome 浏览器(使用 Lighthouse)
  • 文本编辑器
  • 可选:Android 设备上的 Chrome

从 GitHub 下载或克隆 pwa-training-labs 代码库,并安装 Node.js 的 LTS 版本(如果需要)。

转到 offline-quickstart-lab/app/ 目录并启动本地开发服务器:

cd offline-quickstart-lab/app
npm install
node server.js

您可以随时使用 Ctrl-c 终止服务器。

打开浏览器并导航到 localhost:8081/。您应该会看到该网站是一个简单的静态网页。

注意:请取消注册任何 Service Worker 并清除 localhost 的所有 Service Worker 缓存,以免它们干扰实验室。在 Chrome DevTools 中,您可以点击 Application 标签页的 Clear storage 部分中的清除网站数据来执行此操作。

在您的首选文本编辑器中打开 offline-quickstart-lab/app/ 文件夹。您将在其中构建实验的 app/ 文件夹。

此文件夹包含:

  • images/ 文件夹包含示例图片
  • styles/main.css 是主样式表
  • index.html 是我们的示例网站的主 HTML 网页
  • package-lock.jsonpackage.json 跟踪应用依赖项(在本例中,只有依赖项是本地开发服务器)
  • server.js 是用于测试的本地开发服务器
  • service-worker.js 是 Service Worker 文件(当前为空)

在我们开始对网站进行更改之前,请对 Lighthouse 进行审核,看看哪些方面有待改进。

返回应用(在 Chrome 中),然后打开开发者工具审核标签页。您应该会看到 Lighthouse 图标和配置选项。为设备选择“移动设备”,选择所有审核,选择任一节流选项,然后选择清除存储空间

点击运行审核。审核需要一些时间才能完成。

说明

审核完成后,您应该会在开发者工具中看到一份包含得分的报告。系统应显示如下所示的分数(分数可能并不完全相同):

注意:Lighthouse 得分是近似值,可能会受您的环境(例如,如果您打开了大量浏览器窗口)影响。您的得分可能与此处显示的结果不完全相同。

渐进式 Web 应用部分应如下所示:

此报告包含五个类别的得分和指标:

  • 渐进式 Web 应用
  • 性能
  • 无障碍
  • 最佳做法
  • 搜索引擎优化

如您所见,我们的应用在渐进式 Web 应用 (PWA) 类别中的评分不佳。提高我们的分数!

花点时间看一看报告中的 PWA 部分,看看缺少什么内容。

注册 Service Worker

报告中列出的故障之一是未注册任何 Service Worker。我们目前在 app/service-worker.js 中有一个空的 Service Worker 文件。

将以下脚本添加到 index.html 底部紧邻结束 </body> 标记之前的位置:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

说明

此代码会在页面加载后注册空的 service-worker.js Service Worker 文件。不过,当前的 Service Worker 文件是空的,将不执行任何操作。让我们在下一步中添加服务代码。

预缓存资源

报告中列出的另一项失败是应用处于离线状态时未返回 200 状态代码。我们需要更新 Service Worker 才能解决此问题。

将以下代码添加到 Service Worker 文件 (service-worker.js):

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

现在返回浏览器并刷新网站。检查控制台,查看 Service Worker:

  • 已注册
  • 已安装
  • 已启用

注意:如果您之前已经注册了 Service Worker,或者无法触发所有事件,请取消注册任何 Service Worker,然后刷新页面。如果失败,请关闭该应用的所有实例,然后重新打开。

接下来,通过运行 Ctrl + c 在命令行中终止本地开发服务器。再次刷新该网站,您会观察到它在服务器处于离线状态时也能加载!

注意:您可能会看到控制台错误消息,指示无法提取 Service Worker:An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED。出现此错误的原因是浏览器无法提取 Service Worker 脚本(因为网站处于离线状态),但这是正常现象,因为我们无法使用 Service Worker 缓存自身。否则,用户的浏览器会永久地与同一个 Service Worker 卡住!

说明

index.html 中的注册脚本注册 Service Worker 后,会出现 Service Worker install 事件。在此事件期间,install 事件监听器会打开命名的缓存,并缓存使用 cache.addAll 方法指定的文件。这称为“预缓存”,因为它发生在 install 事件中(通常是用户首次访问您的网站时)。

在安装 Service Worker 后,如果另一个 Service Worker 当前未控制页面,则系统会将新的 Service Worker“激活”(在 Service Worker 中触发 activate 事件监听器),并开始控制页面。

当激活的 Service Worker 控制的网页请求资源时,请求将通过网络代理等 Service Worker 进行传递。每个请求都会触发 fetch 事件。在我们的 Service Worker 中,fetch 事件监听器会搜索缓存,并在缓存资源可用时返回缓存的资源。如果资源未缓存,则将正常请求资源。

通过缓存资源,应用可以避免网络请求,从而离线工作。现在,我们的应用在离线时也能响应,并返回 200 状态代码!

注意:除了本示例中的记录,激活事件不可用于任何其他事件。此事件有助于调试 Service Worker 生命周期问题。

可选:您还可以通过展开缓存存储部分,在开发者工具的应用标签页中查看缓存的资源:

使用 node server.js 重启开发服务器,然后刷新网站。然后再次打开开发者工具中的审核标签页,选择新建审核(左上角的加号),重新运行 Lighthouse 审核。审核结束后,您应该会看到我们的 PWA 得分明显更高,但仍有待改进。我们将在下一部分继续提高我们的得分。

注意:此部分为可选步骤,因为测试 Web 应用安装横幅不在实验室的讨论范围内。您可以使用远程调试自行尝试一下。

我们的 PWA 分数仍然不尽如人意。报告中列出的一些遗留错误是:系统不会提示用户安装我们的 Web 应用;并且尚未在地址栏中配置启动画面或品牌颜色。我们可以解决这些问题,并通过满足一些其他条件,逐步实现添加到主屏幕功能。最重要的是,我们需要创建一个清单文件

创建清单文件

app/ 中创建一个名为 manifest.json 的文件,并添加以下代码:

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

清单中已引用了应用中提供的图片。

然后,将以下 HTML 添加到 index.html<head> 标记的底部:

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

返回网站。在开发者工具的应用标签页中,选择清除存储空间部分,然后点击清除网站数据。然后刷新页面。现在,选择 Manifest 部分。您应该会看到在 manifest.json 文件中配置的图标和配置选项。如果您未看到更改,请在无痕式窗口中打开网站,然后再次检查。

说明

manifest.json 文件会告知浏览器如何为应用的一些渐进式方面(例如浏览器 Chrome、主屏幕图标和启动画面)设置样式和格式。该功能还可用于将 Web 应用配置为standalone模式打开,就像原生应用一样(换言之,浏览器外部)。

截至撰写本文时,部分浏览器尚处于开发阶段,<meta> 代码可以为尚未获得全面支持的某些浏览器配置部分功能。

我们必须清除网站数据才能移除旧版 index.html 缓存(因为此版本没有清单链接)。尝试再次运行 Lighthouse 评估,看看 PWA 得分有多大提高!

激活安装提示

安装此应用的下一步是向用户显示安装提示。Chrome 67 会自动提示用户,但从 Chrome 68 开始,系统应以编程方式激活安装提示以响应用户手势。

使用以下代码,在“index.html”顶部添加“安装应用”按钮和横幅(紧跟在 <main> 标记后面):

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

然后,将以下样式添加到 styles/main.css,以设置横幅样式:

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

保存文件。最后,将以下脚本代码添加到 index.html

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

保存文件。在 Android 设备上的 Chrome 中使用远程调试打开应用。网页加载时,您应该会看到“安装应用”按钮(在桌面设备上看不到该应用,因此请务必在移动设备上进行测试)。点击该按钮后,系统应该会弹出“添加到主屏幕”提示。按照相关步骤在设备上安装应用。安装后,您应该能够通过点按新创建的主屏幕图标,以独立模式(在浏览器之外)打开 Web 应用。

说明

“HTML 和 CSS”代码添加了一个隐藏横幅和按钮,用户可以使用它来激活安装提示。

beforeinstallprompt 事件触发后,我们会阻止默认体验(Chrome 67 及更低版本会自动提示用户进行安装),并在全局 deferredPrompt 变量中捕获 beforeinstallevent。然后,“安装应用”按钮会被配置为使用 beforeinstalleventprompt() 方法显示提示。在用户做出选择(是否安装)后,userChoice promise 会根据用户的选择 (outcome) 进行解析。最后,我们会在一切就绪后显示安装按钮。

您已了解如何使用 Lighthouse 审核网站,以及如何实现离线功能的基础知识。学完可选部分后,您还学习了如何将 Web 应用安装到主屏幕!

其他资源

Lighthouse 是开源的!您可以为其创建分支、添加自己的测试和提交 bug。Lighthouse 也是一款命令行工具,用于与构建流程集成。

如需查看 PWA 培训课程中的所有 Codelab,请参阅课程的欢迎 Codelab