使用 Service Workers 来预缓存应用外壳

  • service workers 提供的是一种应该被理解为渐进增强的特性,这些特性仅仅作用于支持service workers 的浏览器。比如,使用 service workers 你可以缓存应用外壳和你的应用所需的数据,所以这些数据在离线的环境下依然可以获得。如果浏览器不支持 service workers ,支持离线的 代码没有工作,用户也能得到一个基本的用户体验。使用特性检测来渐渐增强有一些小的开销,它不会在老旧的不支持 service workers 的浏览器中产生破坏性影响。
  1. 注册 service worker
    第一步,在应用根目录下创建一个空文件叫做 service-worker.js 。这个 service-worker.js 文件必须放在跟目录,因为 service workers 的作用范围是根据其在目录结构中的位置决定的。
    接下来,检查浏览器是否支持 service workers,如果支持,就注册 service worker,将下面代码添加至 app.js中。
  1. if('serviceWorker' in navigator) {
  2. navigator.serviceWorker
  3. .register('/service-worker.js')
  4. .then(function() { console.log('Service Worker Registered'); });
  5. }
  1. 缓存站点的资源
    当 service worker 被注册以后,当用户首次访问页面的时候一个 install 事件会被触发。在这个事件的回调函数中,我们能够缓存所有的应用需要再次用到的资源。
    当 service worker 被激活后,它应该打开缓存对象并将应用外壳需要的资源存储进去。将下面这些代码加入service-worker.js
  1. //缓存版本
  2. //提供缓存名可以给缓存的文件添加版本或将数据分开,方便后续升级数据而不影响其他缓存。
  3. var cacheName = 'weatherPWA-step-6-1';
  4. var filesToCache = [];
  5. self.addEventListener('install', function(e) {
  6. console.log('[ServiceWorker] installed');
  7. e.waitUntil(
  8. //提供一个缓存的名字并利用 caches.open()打开 cache 对象
  9. caches.open(cacheName).then(function(cache){
  10. console.log('[ServiceWorker] Caching app shell');
  11. //一旦缓存被打开,调用 cache.addAll() 并传入一个 url 列表,然后加载这些资源并将响应添加至缓存。
  12. //注意,如果某个文件缓存失败了,那么整个缓存就会失败。
  13. return cache.addAll(filesToCache);
  14. })
  15. )
  16. })
  1. 查看devTool
    使用DevTools来调试service workers。刷新的网页前,开启DevTools,从 Application 的面板中打开 Service Worker 的窗格。然后刷新,出现如下图:
    PWA(Progressive Web App)开发流程 - 图1
    当看到这样的信息,表示该页面有个Service Worker正在运行。
  2. service worker的更新
    现在在service-worker.js里的install 的事件监听器下面添加activate 的事件监听器。
  1. self.addEventListener('activate', function(e) {
  2. console.log('[ServiceWorker] Activate');
  3. });

当 service worker 开始启动时,这将会发射activate事件。
打开DevTools并刷新网页,切换到应用程序面板的Service Worker窗格,在已被激活的Service Worker中单击inspect。理论上,控制台将会出现[ServiceWorker] Activate的信息,但这并没有发生。回到Service Worker窗格,会发现新的Service Worker是在“等待”状态。

PWA(Progressive Web App)开发流程 - 图2

简单来说,旧的Service Worker将会继续控制该网页直到标签被关闭。因此,可以关闭再重新打开该网页或者点击 skipWaiting 的按钮,或者是在DevTools中的Service Worker窗格启用 Update on Reload 。当那个复选框被选择后,当每次页面重新加载,Service Worker将会强制更新。

启用 update on reload 复选框并重新加载页面以确认新的Service Worker被激活。

  1. 添加完整active监听器
  1. self.addEventListener('activate', function(e) {
  2. console.log('[ServiceWorker] Activate');
  3. e.waitUntil(
  4. caches.keys().then(function(keyList) {
  5. return Promise.all(keyList.map(function(key) {
  6. console.log('[ServiceWorker] Removing old cache', key);
  7. if (key !== cacheName) {
  8. return caches.delete(key);
  9. }
  10. }));
  11. })
  12. );
  13. });

确保在每次修改了 service worker 后修改 cacheName,这能确保你永远能够从缓存中获得到最新版本的文件。过一段时间清理一下缓存删除掉没用的数据也是很重要的。

最后,让我们更新一下 app shell 需要的缓存的文件列表。在这个数组中,我们需要包括所有我们的应用需要的文件,其中包括图片、JavaScript以及样式表等等。

  1. var filesToCache = [
  2. '/',
  3. '/index.html',
  4. '/scripts/app.js',
  5. '/styles/inline.css',
  6. '/images/clear.png',
  7. '/images/cloudy-scattered-showers.png',
  8. '/images/cloudy.png',
  9. '/images/fog.png',
  10. '/images/ic_add_white_24px.svg',
  11. '/images/ic_refresh_white_24px.svg',
  12. '/images/partly-cloudy.png',
  13. '/images/rain.png',
  14. '/images/scattered-showers.png',
  15. '/images/sleet.png',
  16. '/images/snow.png',
  17. '/images/thunderstorm.png',
  18. '/images/wind.png'
  19. ];

我们的应用目前还不能离线工作。我们缓存了 app shell 的组件,但是我们仍然需要从本地缓存中加载它们。

  1. 从缓存中加载 app sheel
    Service workers 可以截获 Progressive Web App 发起的请求并从缓存中返回响应。这意味着我们能够 决定如何来处理这些请求,以及决定哪些网络响应能够成为我们的缓存。
    让我们来从缓存中加载 app shell。将下面代码加入 service-worker.js 中:
  1. self.addEventListener('fetch', function(e) {
  2. console.log('[ServiceWorker] Fetch', e.request.url);
  3. e.respondWith(
  4. caches.match(e.request).then(function(response) {
  5. return response || fetch(e.request);
  6. })
  7. );
  8. });

从内至外,caches.match() 从网络请求触发的 fetch 事件中得到请求内容,并判断请求的资源是 否存在于缓存中。然后以缓存中的内容作为响应,或者使用 fetch 函数来加载资源(如果缓存中没有该资源)。 response 最后通过 e.respondWith() 返回给 web 页面。

  1. 测试

先刷新那个网页, 然后去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 不会接管控制权,知道该页面重新刷新后,除非你使用立刻控制的方式。