此 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 代码库,并根据需要安装 LTS 版本的 Node.js。
进入 offline-quickstart-lab/app/ 目录并启动本地开发服务器:
cd offline-quickstart-lab/app npm install node server.js
您可以使用 Ctrl-c 随时终止服务器。
打开浏览器,然后前往 localhost:8081/。您应该会看到该网站是一个简单的静态网页。
注意:请取消注册所有 Service Worker 并清除 localhost 的所有 Service Worker 缓存,以免它们干扰实验。在 Chrome 开发者工具中,您可以通过点击应用标签页的清除存储空间部分中的清除网站数据来实现此目的。
在首选文本编辑器中打开 offline-quickstart-lab/app/ 文件夹。您将在 app/ 文件夹中构建实验。
此文件夹包含:
images/文件夹包含示例图片styles/main.css是主要样式表index.html是我们示例网站的主要 HTML 网页package-lock.json和package.json跟踪应用依赖项(在本例中,唯一的依赖项是本地开发服务器)server.js是用于测试的本地开发服务器service-worker.js是服务工作线程文件(目前为空)
在开始更改网站之前,我们先使用 Lighthouse 进行审核,看看哪些方面可以改进。
返回到应用(在 Chrome 中),然后打开开发者工具的审核标签页。您应该会看到 Lighthouse 图标和配置选项。为设备选择“移动设备”,选择所有审核,选择任一节流选项,然后选择清除存储空间:

点击运行审核。审核需要一些时间才能完成。
说明
审核完成后,您应该会在开发者工具中看到包含得分的报告。它应显示得分,如下所示(得分可能不完全相同):
注意:Lighthouse 分数是近似值,可能会受到环境的影响(例如,如果您打开了大量浏览器窗口)。您的得分可能与此处显示的得分不完全相同。

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

该报告包含五个类别的得分和指标:
- 渐进式 Web 应用
- 性能
- 无障碍
- 最佳做法
- 搜索引擎优化 (SEO)
如您所见,我们的应用在渐进式 Web 应用 (PWA) 类别中的得分较低。让我们来提高得分吧!
花点时间查看报告的 PWA 部分,看看缺少了哪些内容。
注册 Service Worker
报告中列出的一个失败是未注册任何 Service Worker。我们目前在 app/service-worker.js 中有一个空的服务工作线程文件。
将以下脚本添加到 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 文件。不过,当前的服务工作线程文件为空,不会执行任何操作。我们将在下一步中添加服务代码。
预缓存资源
报告中列出的另一个失败是,应用在离线时未以 200 状态代码进行响应。我们需要更新服务工作线程来解决此问题。
将以下代码添加到 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);
})
);
});现在,返回浏览器并刷新网站。检查控制台,确保服务工作线程:
- 已注册
- 已安装
- 已启用
注意:如果您之前已注册过服务工作器,或者在触发所有事件时遇到问题,请取消注册所有服务工作器并刷新页面。如果此方法无效,请关闭应用的所有实例,然后重新打开应用。
接下来,在命令行中运行 Ctrl + c 以终止本地开发服务器。再次刷新该网站,并观察到即使服务器处于离线状态,该网站也会加载!
注意:您可能会看到控制台错误,指出无法提取 service worker:An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED。之所以显示此错误,是因为浏览器无法获取服务工作线程脚本(因为网站处于离线状态),但这属于预期行为,因为我们无法使用服务工作线程来缓存自身。否则,用户的浏览器将永远卡在同一服务工作线程上!
说明
当注册脚本在 index.html 中注册服务工作器后,系统会触发服务工作器的 install 事件。在此事件期间,install 事件监听器会打开一个命名缓存,并缓存使用 cache.addAll 方法指定的文件。这称为“预缓存”,因为它发生在 install 事件期间,而该事件通常是用户首次访问您的网站时发生的。
安装 Service Worker 后,如果当前没有其他 Service Worker 控制网页,则新的 Service Worker 会被“激活”(Service Worker 中的 activate 事件监听器会被触发),并开始控制网页。
当已激活的服务工作线程所控制的网页请求资源时,请求会像网络代理一样通过服务工作线程。系统会为每个请求触发 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。然后,将“安装应用”按钮配置为通过 beforeinstallevent 的 prompt() 方法显示提示。用户做出选择(安装或不安装)后,userChoice promise 会根据用户的选择 (outcome) 进行解析。最后,当一切就绪后,我们会显示安装按钮。
您已了解如何使用 Lighthouse 审核网站,以及如何实现离线功能的基础知识。如果您完成了可选部分,还学习了如何将 Web 应用安装到主屏幕!
更多资源
Lighthouse 是开源的!您可以派生该代码库,添加自己的测试,并提交 bug。Lighthouse 还可作为命令行工具与构建流程集成。
如需查看 PWA 培训课程中的所有 Codelab,请参阅本课程的欢迎 Codelab。