使用 Service Workers 来预缓存应用外壳
service workers
提供的是一种应该被理解为渐进增强的特性,这些特性仅仅作用于支持service workers 的浏览器。比如,使用 service workers 你可以缓存应用外壳和你的应用所需的数据,所以这些数据在离线的环境下依然可以获得。如果浏览器不支持 service workers ,支持离线的 代码没有工作,用户也能得到一个基本的用户体验。使用特性检测来渐渐增强有一些小的开销,它不会在老旧的不支持 service workers 的浏览器中产生破坏性影响。
- 注册 service worker
第一步,在应用根目录下创建一个空文件叫做service-worker.js
。这个service-worker.js
文件必须放在跟目录,因为 service workers 的作用范围是根据其在目录结构中的位置决定的。
接下来,检查浏览器是否支持 service workers,如果支持,就注册 service worker,将下面代码添加至app.js
中。
if('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/service-worker.js')
.then(function() { console.log('Service Worker Registered'); });
}
- 缓存站点的资源
当 service worker 被注册以后,当用户首次访问页面的时候一个 install 事件会被触发。在这个事件的回调函数中,我们能够缓存所有的应用需要再次用到的资源。
当 service worker 被激活后,它应该打开缓存对象并将应用外壳需要的资源存储进去。将下面这些代码加入service-worker.js
//缓存版本
//提供缓存名可以给缓存的文件添加版本或将数据分开,方便后续升级数据而不影响其他缓存。
var cacheName = 'weatherPWA-step-6-1';
var filesToCache = [];
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] installed');
e.waitUntil(
//提供一个缓存的名字并利用 caches.open()打开 cache 对象
caches.open(cacheName).then(function(cache){
console.log('[ServiceWorker] Caching app shell');
//一旦缓存被打开,调用 cache.addAll() 并传入一个 url 列表,然后加载这些资源并将响应添加至缓存。
//注意,如果某个文件缓存失败了,那么整个缓存就会失败。
return cache.addAll(filesToCache);
})
)
})
- 查看devTool
使用DevTools来调试service workers。刷新的网页前,开启DevTools,从 Application 的面板中打开 Service Worker 的窗格。然后刷新,出现如下图:
当看到这样的信息,表示该页面有个Service Worker正在运行。 - service worker的更新
现在在service-worker.js里的install 的事件监听器下面添加activate 的事件监听器。
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
});
当 service worker 开始启动时,这将会发射activate事件。
打开DevTools并刷新网页,切换到应用程序面板的Service Worker窗格,在已被激活的Service Worker中单击inspect。理论上,控制台将会出现[ServiceWorker] Activate
的信息,但这并没有发生。回到Service Worker窗格,会发现新的Service Worker是在“等待”状态。
简单来说,旧的Service Worker将会继续控制该网页直到标签被关闭。因此,可以关闭再重新打开该网页或者点击 skipWaiting 的按钮,或者是在DevTools中的Service Worker窗格启用 Update on Reload 。当那个复选框被选择后,当每次页面重新加载,Service Worker将会强制更新。
启用 update on reload 复选框并重新加载页面以确认新的Service Worker被激活。
- 添加完整active监听器
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
console.log('[ServiceWorker] Removing old cache', key);
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
);
});
确保在每次修改了 service worker 后修改 cacheName
,这能确保你永远能够从缓存中获得到最新版本的文件。过一段时间清理一下缓存删除掉没用的数据也是很重要的。
最后,让我们更新一下 app shell
需要的缓存的文件列表。在这个数组中,我们需要包括所有我们的应用需要的文件,其中包括图片、JavaScript以及样式表等等。
var filesToCache = [
'/',
'/index.html',
'/scripts/app.js',
'/styles/inline.css',
'/images/clear.png',
'/images/cloudy-scattered-showers.png',
'/images/cloudy.png',
'/images/fog.png',
'/images/ic_add_white_24px.svg',
'/images/ic_refresh_white_24px.svg',
'/images/partly-cloudy.png',
'/images/rain.png',
'/images/scattered-showers.png',
'/images/sleet.png',
'/images/snow.png',
'/images/thunderstorm.png',
'/images/wind.png'
];
我们的应用目前还不能离线工作。我们缓存了 app shell 的组件,但是我们仍然需要从本地缓存中加载它们。
- 从缓存中加载 app sheel
Service workers 可以截获 Progressive Web App 发起的请求并从缓存中返回响应。这意味着我们能够 决定如何来处理这些请求,以及决定哪些网络响应能够成为我们的缓存。
让我们来从缓存中加载 app shell。将下面代码加入service-worker.js
中:
self.addEventListener('fetch', function(e) {
console.log('[ServiceWorker] Fetch', e.request.url);
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
从内至外,caches.match() 从网络请求触发的 fetch 事件中得到请求内容,并判断请求的资源是 否存在于缓存中。然后以缓存中的内容作为响应,或者使用 fetch 函数来加载资源(如果缓存中没有该资源)。 response 最后通过 e.respondWith() 返回给 web 页面。
- 测试
先刷新那个网页, 然后去DevTools里的 Cache Storage 窗格中的 Application 面板上。展开该部分,你应该会在左边看到您的app shell缓存的名称。当你点击你的appshell缓存,你将会看到所有已经被缓存的资源。
下一步骤是修改该应用程序和service worker的逻辑,让气象数据能够被缓存,并能在应用程序处于离线状态,将最新的缓存数据显示出来。
Tip: 如果要清除所有保存的数据(localStoarge,IndexedDB的数据,缓存文件),并删除任何的service worker,可以在DevTools中的Application 面板里的Clear storage清除。
- 一旦 service worker 被注销(unregistered)。它会继续作用直到浏览器关闭。
- 如果你的应用打开了多个窗口,新的 service worker 不会工作,直到所有的窗口都进行了刷新,使用了 新的 service worker。
- 注销一个 service worker 不会清空缓存,所以如果缓存名没有修改,你可能继续获得到旧的数据。
- 如果一个 service worker 已经存在,而且另外一个新的 service worker 已经注册了,这个新的 service worker 不会接管控制权,知道该页面重新刷新后,除非你使用立刻控制的方式。