前言:今天结识了google PWA提供的一个对移动端Web应用提供离线体验的一个功能,感受颇有用。我这里不分享本身的写法和代码。官网文档说的很详细,直接粘过来你们看吧。css
推荐官网地址:你的第一个渐进式 Web 应用(Progressive Web App - PWA)html
文章详情以下:node
渐进式 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
beforeinstallprompt
事件告诉用户它是可安装的。
Warning:为了简化此 codelab ,并解释提供离线体验的基础知识,咱们使用的是原生 JavaScript。在生产应用中,咱们强烈建议使用 Workbox 工具来构建 Service Worker 。它能够帮你消除许多可能遇到的坑和死角。
此 codelab 专一于渐进式 Web 应用。屏蔽了不相关的概念和代码块,并为你提供简单的复制和粘贴。
最近版本的 Chrome(74 或更高版本)
PWA 只是个 Web 应用,所以适用于全部浏览器,但咱们将使用 Chrome DevTools 的一些功能来更好地了解浏览器层面的状况,并用它来测试安装体验。
了解 HTML,CSS,JavaScript 和Chrome DevTools 。
咱们的天气数据来自 Dark Sky API。要使用它,你须要申请 API 密钥。它很容易使用,而且能够免费用于非商业项目。
Note: 你还能够在没有 Dark Sky API 密钥的状况下完成此 codelab。若是咱们的服务器没法从 Dark Sky API 获取真实数据,它将返回假数据。
要测试你的 API 密钥是否正常工做,请向 DarkSky API 发出 HTTP 请求。修改如下网址,将 DARKSKY_API_KEY
替换为你的 API 密钥。若是一切正常,你应该看到纽约市的最新天气预报。
https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319
咱们已将此项目所需的一切都放入 Git 仓库中。首先,你须要获取代码并在你喜欢的开发环境中打开它。对于此代码库,咱们建议使用 Glitch。
推荐用 Glitch 来使用此代码库。
.env
文件,并使用 DarkSky API 密钥来更新它。若是你想下载代码并在本地工做,你须要安装好最新版本的 Node 和代码编辑器。
Caution: 若是你在本地工做,某些 Lighthouse 审计可能没法经过,甚至可能没法安装,由于本地服务器并无在安全环境下运行。
npm install
以安装运行服务器所需的依赖项。server.js
并设置 DarkSky API 密钥。node server.js
以在端口 8000 上启动服务器.咱们的起点是为此 codelab 设计的基本天气应用。代码已经大幅简化,以突显此代码库中的概念,而且它几乎没有作错误处理。若是你选择在生产应用中复用此代码,请确保处理各类错误并彻底测试全部代码。
咱们将试着......
server.js
中的 FORECAST_DELAY
为天气预报服务器添加延迟Lighthouse是一款易于使用的工具,可帮助你提升网站和网页的质量。它可用来对性能,可访问性,渐进式 Web 应用等进行审计。每种审计及都有一个参考文档,解释了该审计为什么重要,以及如何解决所发现的问题。
咱们将使用 Lighthouse 来审计咱们的天气应用,并验证咱们所作的更改。
Note: 你能够在 Chrome DevTools 中,以命令行或 Node 模块的方式运行 Lighthouse。考虑将Lighthouse 添加到你的构建流程中,以确保你的 Web 应用不会出现回归问题。
咱们将重点关注渐进式 Web 应用的审计结果。
这里有不少红色信息须要关注:
start_url
未给出 200 响应。start_url
的 Service Worker。让咱们进入并开始修复其中的一些问题!
到本节结束时,咱们的天气应用将经过如下审计:
Web 应用清单是一个简单的 JSON 文件,它使开发人员可以控制本应用对用户的显示方式。
使用 Web 应用清单,你的 Web 应用能够:
display
)。start_url
)。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。
接下来,咱们须要经过向应用中的每一个页面添加 <link rel="manifest"...
来把此清单告知浏览器。把下列代码行添加到 index.html
文件中的 <head>
元素下。
<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">
DevTools 提供了一种快速简便的方法来检查你的 manifest.json
文件。打开 Application 面板上的 Manifest 窗格。若是你已正确添加清单信息,你将可以在此窗格中看到它以对人类友好的格式进行解析和显示。
iOS 上的 Safari 不支持 Web 应用清单( 至少目前为止 ),所以你须要将传统的 meta
标签添加到 index.html
文件的 <head>
中:
<!-- 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 审计还提出了一些其它很容易解决的问题,因此让咱们先来处理下这些问题。
根据 SEO 审计,Lighthouse 提出咱们的 “ Document does not have a meta description. ”。描述能够显示在 Google 的搜索结果中。高质量,独特的描述可使你的搜索结果与搜索用户更相关,并能够增长搜索流量。
要添加描述,请将如下 meta
标记添加到文档的 <head>
中:
<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">
在 PWA 审计中,Lighthouse 提出咱们的应用“ Does not set an address-bar theme color ”。将浏览器的地址栏设置为与你的品牌色相匹配,能够提供更加沉浸式的用户体验。
要在移动设备上设置主题颜色,请将如下 meta
标记添加到文档的 <head>
中:
<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />
再次运行 Lighthouse(经过单击“审计”窗格左上角的+号)并验证你的更改。
SEO 审计
*✅经过: 文档已有元描述。
渐进式应用审计
start_url
未给出 200 响应。start_url
的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。用户会期待所安装的应用在离线时始终具备基线体验。所以对于可安装的 Web 应用来讲,永远不会显示 Chrome 的离线恐龙图是相当重要的。离线体验的范围包括简单的离线页面、具备先前缓存数据的只读体验,甚至具备彻底功能的离线体验(在网络链接恢复时自动同步)。
在本节中,咱们将向天气应用添加一个简单的离线页面。若是用户在离线时尝试加载应用,则会显示咱们的自定义页面,而不是浏览器显示的典型离线页面。到本节结束时,咱们的天气应用将经过如下审计:
start_url
未给出 200 响应。start_url
的 Service Worker。在下一部分中,咱们将使用完整的离线体验替换咱们的自定义离线页面。这将改善离线体验,但更重要的是,它将显著提升咱们的性能,由于咱们的大多数资产(HTML,CSS 和 JavaScript)都将在本地存储和提供,从而消除了网络方面的潜在瓶颈。
若是你对 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 。将如下代码添加到 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 将控制对该域中全部网页的请求。
首先,咱们须要告诉 Service Worker 缓存什么。咱们已经建立了一个简单的离线页面 ( public/offline.html
),只要没有网络链接,咱们就会显示它。
在 service-worker.js
中,将 '/offline.html',
添加到 FILES_TO_CACHE
数组中,最终结果应以下所示:
// CODELAB: Update cache names any time any of the cached files change.
const FILES_TO_CACHE = [
'/offline.html',
];
接下来,咱们须要修改 install
事件以告知 Service Worker 预先缓存离线页面:
// 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 来理解和调试 Service Worker 。在从新加载页面以前,打开 DevTools,转到 Application面板上的 Service Workers 窗格。它应该以下所示:
当你看到这样的空白页面时,表示当前打开的页面中没有任何已注册的 Service Worker 。
如今,从新加载页面,“Service Worker” 窗格应以下所示:
当你看到这样的信息时,表示该页面正在运行 Service Worker 。
状态标签旁边有一个数字(这里是34251),在你使用 Service Worker 时,请密切注意该数字。这是一种判断你的 Service Worker 是否已更新的简单方式。
咱们将使用 activate
事件来清理缓存中的任何旧数据。此代码可确保你的 Service Worker 在任何应用外壳文件发生更改时更新其缓存。为了使其工做,你须要在 Service Worker 文件的顶部增长 CACHE_NAME
变量。
将如下代码添加到 activate
事件中:
// 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);
}
}));
})
);
打开 “Service Worker” 窗格,刷新页面,你将看到安装了新的 Service Worker ,而且状态编号会递增。
更新后的 Service Worker 当即得到控制权,由于咱们的 install
事件以 self.skipWaiting()
结束, activate
事件以 self.clients.claim()
结束。若是没有这些,只要有一个打开着此页面的选项卡,旧的 Service Worker 就会继续控制此页面。
最后,咱们须要处理 fetch
事件。咱们将使用 "网络优先,回退到缓存" 的策略 。 Service Worker 将首先尝试从网络获取资源,若是失败,它将从缓存中返回离线页面。
// 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()
,你只会得到默认的网络行为。
让咱们检查一下,确保一切正常。打开 “Service Worker” 窗格,刷新页面,你将看到安装了新的 Service Worker ,而且状态编号会增长。
咱们还能够查看已缓存的内容。转到 DevTools 的 Application 面板上的 Cache Storage 窗格。右键单击 Cache Storage,选择 Refresh Caches,展开该部分,你应该会在左侧看到静态缓存的名称。单击缓存名称将显示缓存的全部文件。
如今,咱们来测试下离线模式。返回 DevTools 的 Service Workers 窗格并检查 Offline 复选框。检查后,你应该会在 Network 面板选项卡旁边看到一个黄色警告图标。这表示你处于离线状态。
从新加载你的页面......能够了!咱们获得咱们的离线熊猫图,而不是 Chrome 的离线恐龙图!
当涉及缓存时,调试 Service Worker 多是一个挑战,若是缓存未按预期更新,事情可能变成噩梦。在典型的 Service Worker 生命周期和代码中的错误中,你可能会很快感到沮丧。可是,没必要如此。
在 “Application” 面板的“ Service Worker ”窗格中,有一些复选框可让你更轻松。
在某些状况下,你可能会发现本身正在加载已缓存的数据或者没能按预期更新内容。要清除全部已保存的数据(localStorage,indexedDB 数据,缓存文件)并删除任何 Service Worker ,请使用 “Application” 选项卡中的 “Clear storage” 窗格。或者,你也可使用隐身窗口。
其它提示:
再次运行 Lighthouse 并验证你的更改。在验证更改以前,请不要忘记取消 “Offline” 复选框!
SEO 审计
*✅经过: 文档已有元描述。
渐进式应用审计
*✅经过: 在离线时当前页面未给出 200 响应。 *✅经过: 在离线时 start_url
未给出 200 响应。 *✅经过: 未注册用来控制页面和 start_url
的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。
将手机开启飞行模式,而后尝试运行一些你喜欢的应用。几乎在全部状况下,它们都提供了至关强大的离线体验。用户但愿他们的应用具备稳健的体验,Web 应用也不例外。渐进式 Web 应用在设计时应该把离线做为核心场景。
Key Point:离线优先的设计能够经过减小应用发出的网络请求数量并改用预先缓存资源并直接从本地缓存提供资源来大幅提升Web 应用的性能。即便用最快的网络链接,从本地缓存提供的服务也仍然会更快!
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 时预先缓存全部静态资源,以便把咱们运行应用所需的一切都存储在用户的设备上。为了确保咱们的应用快速加载,咱们将使用缓存优先策略:不去网络获取资源,而是从本地缓存中取出;只有当缓存不可用时,咱们才会尝试从网络中获取它。
从本地缓存中取可消除任何网络方面的变数。不管用户使用何种网络(WiFi,5G,3G 甚至 2G),咱们须要运行的关键资源都几乎能够当即使用。
Caution: 在此示例中,使用 cache-first
策略提供静态资源,这会致使在不询问网络的状况下返回任何缓存内容的副本。虽然 cache-first
策略易于实施,但它可能会在未来的演化中带来挑战。
stale-while-revalidate strategy 对某些类型的数据是理想的,好比本应用。它会尽量快地获取屏幕上要显示的数据,而后在网络返回最新数据后进行更新。 Stale-while-revalidate 意味着咱们须要发起两个异步请求,一个到缓存,一个到网络。
在正常状况下,缓存数据几乎会当即返回,为应用提供可使用的最新数据。而后,当网络请求返回时,将使用来自网络的最新数据更新应用。
对于咱们的应用,这提供了比 "网络优先,回退到缓存" 策略更好的体验,由于用户没必要等到网络请求超时后才在屏幕上看到某些内容。他们最初可能会看到较旧的数据,但一旦网络请求返回,应用就将使用最新数据进行更新。
如前所述,应用须要启动两个异步请求,一个到缓存,一个到网络。该应用使用 window
上的 caches
对象来访问缓存并获取最新数据。这是渐进加强的一个很好的例子,由于 caches
对象可能并不是在全部浏览器中均可用,若是不可用,网络请求仍然能够工做。
更新 getForecastFromCache()
函数,检查 caches
对象是否在全局 window
对象中可用,若是是,请从缓存中请求数据。
// 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()
以获取缓存的最新天气预报:
// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
.then((forecast) => {
renderForecast(card, forecast);
});
咱们的天气应用如今发出两个异步数据请求,一个来自缓存,另外一个来自 fetch
。若是缓存中有数据,它将被很是快速地返回和渲染(几十毫秒)。而后,当 fetch
响应时,将使用直接来自天气 API 的最新数据更新卡片。
请注意缓存请求和 fetch
请求如何结束于更新天气预报卡片的调用。应用要如何知道它是否显示了最新的数据?这在 renderForecast()
的以下代码中处理:
// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
return;
}
每次更新卡片时,应用都会将数据的时间戳存储在卡片上的隐藏属性中。若是卡片上已存在的时间戳比传递给函数的数据新,应用就什么也不作。
在 Service Worker 中,让咱们添加一个 DATA_CACHE_NAME
以便咱们能够将应用数据与应用外壳分开。更新应用外壳并清除旧缓存后,咱们的数据将保持不变,仍用于超快速加载。请记住,若是你的数据格式未来发生了变化,你就须要一种方法来处理这种状况,并确保应用外壳和内容保持同步。
// 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
数组改成以下文件列表:
// 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
事件不当心删除数据,在 service-worker.js
的 activate
事件,把 if (key !== CACHE_NAME) {
改成:
if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {
咱们须要修改 Service Worker 以拦截对 weather API 的请求并将其响应存储在缓存中,以便咱们之后能够轻松地访问它们。在 stale-while-revalidate 策略中,咱们但愿把网络响应做为“真相之源”,始终由它向咱们提供最新信息。若是不能,也能够失败,由于咱们已经在应用中检索到了最新的缓存数据。
更新 fetch
事件处理程序以便和其它对数据 API 的请求分开。
// 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 窗格。展开该部分,你应该会在左侧看到静态缓存和数据缓存的名称。打开数据缓存会显示为每一个城市存储的数据。
而后,打开 DevTools 并切换到 Service Workers 窗格,选中 Offline 复选框,而后尝试从新加载页面,而后离线并从新加载页面。
若是你是一个快速的网络上,并但愿看看天气预报数据会如何在慢速链接上更新,请在 server.js
中把 FORECAST_DELAY
设置为 5000
。这样对天气预报 API 的全部请求都将延迟 5000 毫秒。
再次运行 Lighthouse 是个好主意。
SEO 审计
*✅经过: 文档已有元描述。
渐进式应用审计
*✅经过: 在离线时当前页面未给出 200 响应。 *✅经过: 在离线时 start_url
未给出 200 响应。 *✅经过: 未注册用来控制页面和 start_url
的 Service Worker。 *✅经过: Web 应用清单符合可安装性要求。 *✅经过: 已配置自定义闪屏。 *✅经过: 已设置地址栏的主题颜色。
安装渐进式 Web 应用后,其外观和行为会与全部其它已安装的应用相似。它与其它应用启动时的位置相同。它在没有地址栏或其它浏览器 UI 的应用中运行。与全部其它已安装的应用同样,它是任务切换器中的顶级应用。
在 Chrome 中,能够经过其 "三点菜单" 来安装渐进式 Web 应用,也能够向用户提供按钮或其它 UI 组件,以提示他们安装你的应用。
Success: 因为 Chrome 的 "三点菜单" 的安装体验不够显眼,咱们建议你在应用中提供一些指示以通知用户你的应用能够安装,并使用安装按钮来完成安装过程。
为了使用户可以安装渐进式 Web 应用,它须要知足一些条件 。最简单的方法是使用 Lighthouse 来确保它符合可安装的标准。
若是你使用此 codelab,你的 PWA 就已经符合这些标准了。
Key Point:对于本节,在 DevTools 的 Application 面板的 Service Workers 窗格中启用 Bypass for network 复选框。选中后,请求将绕过 Service Worker 并直接发送到网络。这简化了咱们的开发过程,由于咱们在完成本节中的任务时没必要更新咱们的 Service Worker 。
首先,让咱们将 install.js
添加到 index.html
文件中。
<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>
beforeinstallprompt
事件若是符合添加到主屏幕条件 ,Chrome 将触发 beforeinstallprompt
事件,你可使用该事件指示你的应用能够“安装”,而后提示用户安装它。添加以下代码以监听 beforeinstallprompt
事件:
// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);
在咱们的 saveBeforeInstallPromptEvent
函数中,咱们将保存对 beforeinstallprompt
事件的引用,以便咱们稍后能够调用它的 prompt()
,并修改咱们的 UI 以显示安装按钮。
// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');
当用户单击安装按钮时,咱们须要调用保存的 beforeinstallprompt
事件的 .prompt()
函数。咱们还须要隐藏安装按钮,由于 .prompt()
只能在每一个保存的事件上调用一次。
// 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
属性的对象。
// 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 事件。
// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);
而后,咱们须要修改 logAppInstalled
函数,对于这个 codelab,咱们只用了 console.log
,但在生产应用中,你可能但愿将其做为事件记录在你的分析软件中。
// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);
不要忘记修改 service-worker.js
文件中的 CACHE_NAME
,由于你对已缓存的文件作了些更改。在 DevTools 的 “Application” 面板上的 “Service Worker” 窗格中启用 Bypass for network 复选框只在开发中有用,在现实世界中是无济于事的。
让咱们看看咱们的安装步骤是如何进行的。为了安全起见,使用 DevTools 应用面板中的 Clear site storage 按钮清除全部内容以确保咱们从头开始。若是你以前安装过该应用,请务必将其卸载,不然安装图标将不会再次显示。
首先,验证咱们的安装图标是否正确显示了,请务必同时在桌面和移动设备上试用。
接下来,让咱们确保一切都安装正确,并正确触发了咱们的事件。你能够在桌面设备或移动设备上执行此操做。若是你想在移动设备上进行测试,请确保使用远程调试,以便查看控制台中的日志。
请注意,若是你从 localhost 在桌面上运行,则你安装的 PWA 可能会显示地址标题,由于 localhost 不认为是安全主机。
咱们还要检查它在 iOS 上的行为。若是你有 iOS 设备,能够直接使用它;或者若是你使用的是 Mac,能够尝试使用 Xcode 提供的 iOS 模拟器。
媒体查询 display-mode
能够根据应用的启动方式来应用样式,或者使用 JavaScript 来断定它是如何启动的。
@media all and (display-mode: standalone) {
body {
background-color: yellow;
}
}
你还能够在 JavaScript 中检测它是否运行在独立模式下来检查这个 display-mode
媒体查询 。
请记住,若是已经安装了应用,则 beforeinstallevent
不会触发,所以在开发期间,你可能须要屡次安装和卸载应用,以确保一切正常运行。
在 Android 上,卸载 PWA 的方式与卸载其它已安装的应用的方式相同。
在 ChromeOS 上,能够从启动器搜索框轻松卸载 PWA。
在 Mac 和 Windows 上,必须经过 Chrome 卸载 PWA。
恭喜,你已经成功构建了第一个渐进式 Web 应用!
你添加了一个 Web 应用清单以使其可以安装,并添加了一个 Service Worker 以确保你的 PWA 始终快速并且可靠。你学习了如何使用 DevTools 审计应用以及如何用它帮你改善用户体验。
你如今知道了将任何 Web 应用转换为渐进式 Web 应用所需的关键步骤。