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

Progressive Web Apps 是快速且可安装的,这意味着它能在在线、离线、断断续续或者缓慢的网络环境下使用。为了实现这个目标,咱们须要使用一个 service worker 来缓存应用外壳,以保证它能始终迅速可用且可靠。css

若是你对 service workers 不熟悉,你能够经过阅读 介绍 Service Workers 来了解关于它能作什么,它的生命周期是如何工做的等等知识。html

service workers 提供的是一种应该被理解为渐进加强的特性,这些特性仅仅做用于支持service workers 的浏览器。好比,使用 service workers 你能够缓存应用外壳和你的应用所需的数据,因此这些数据在离线的环境下依然能够得到。若是浏览器不支持 service workers ,支持离线的 代码没有工做,用户也能获得一个基本的用户体验。使用特性检测来渐渐加强有一些小的开销,它不会在老旧的不支持 service workers 的浏览器中产生破坏性影响。web

注册 service worker

为了让应用离线工做,要作的第一件事是注册一个 service worker,一段容许在后台运行的脚本,不须要 用户打开 web 页面,也不须要其余交互。shell

这只须要简单两步:数组

  1. 建立一个 JavaScript 文件做为 service worker
  2. 告诉浏览器注册这个 JavaScript 文件为 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 (你能够在your-first-pwapp-master/work中找到) :app

 
var cacheName = 'weatherPWA-step-6-1';
var filesToCache = [];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching app shell');
      return cache.addAll(filesToCache);
    })
  );
});

首先,咱们须要提供一个缓存的名字并利用 caches.open()打开 cache 对象。提供的缓存名容许咱们给 缓存的文件添加版本,或者将数据分开,以致于咱们可以轻松地升级数据而不影响其余的缓存。svg

一旦缓存被打开,咱们能够调用 cache.addAll() 并传入一个 url 列表,而后加载这些资源并将响应添加至缓存。不幸的是 cache.addAll() 是原子操做,若是某个文件缓存失败了,那么整个缓存就会失败!

好的。让咱们开始熟悉如何使用DevTools并学习如何使用DevTools来调试service workers。在刷新你的网页前,开启DevTools,从 Application 的面板中打开 Service Worker 的窗格。它应该是这样的:

当你看到这样的空白页,这意味着当前打开的页面没有已经被注册的Service Worker。

如今,从新加载页面。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被激活。

Note: 您可能会在应用程序面板里的Service Worker窗格中看到相似于下面的错误信息,但你能够放心的忽略那个错误信息。

Ok, 如今让咱们来完成activate 的事件处理函数的代码以更新缓存。

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 发起的请求并从缓存中返回响应。这意味着咱们可以 决定如何来处理这些请求,以及决定哪些网络响应可以成为咱们的缓存。

好比:

self.addEventListener('fetch', function(event) {  
  // Do something interesting with the fetch here  
});
让咱们来从缓存中加载 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缓存,你将会看到全部已经被缓存的资源。

如今,让咱们测试离线模式。回去DevTools中的 Service Worker 窗格,启用 Offline 的复选框。启用以后,你将会在 Network 窗格的旁边看到一个黄色的警告图标。这表示您处于离线状态。

刷新网页,而后你会发现你的网页仍然能够正常操做!

下一步骤是修改该应用程序和service worker的逻辑,让气象数据可以被缓存,并能在应用程序处于离线状态,将最新的缓存数据显示出来。

Tip: 若是你要清除全部保存的数据(localStoarge,IndexedDB的数据,缓存文件),并删除任何的service worker,你能够在DevTools中的Application 面板里的Clear storage清除。


小心边缘问题

以前提到过,这段代码 必定不要用在生产环境下 ,由于有不少没有处理的边界状况。

缓存依赖于每次修改内容后更新缓存名称

好比缓存方法须要你在每次改变内容后更新缓存的名字。不然,缓存不会被更新,旧的内容会一直被缓存返回。 因此,请确保每次修改你的项目后更新缓存名称。

每次修改后全部资源都须要被从新下载

另外一个缺点是当一个文件被修改后,整个缓存都须要被从新下载。这意味着即便你修改了一个简单的拼写错误 也会让整个缓存从新下载。这不过高效。

浏览器的缓存可能阻碍 service worker 的缓存的更新

另一个重要的警告。首次安装时请求的资源是直接经由 HTTPS 的,这个时候浏览器不会返回缓存的资源, 除此以外,浏览器可能返回旧的缓存资源,这致使 service worker 的缓存不会获得 更新。

在生产环境中当下 cache-first 策略

咱们的应用使用了优先缓存的策略,这致使全部后续请求都会从缓存中返回而不询问网络。优先缓存的策略是 很容易实现的,但也会为将来带来诸多挑战。一旦主页和注册的 service worker 被缓存下来,将会很难 去修改 service worker 的配置(由于配置依赖于它的位置),你会发现你部署的站点很难被升级。

我该如何避免这些边缘问题

咱们该如何避免这些边缘问题呢? 使用一个库,好比 sw-precache, 它对资源什么时候过时提供了 精细的控制,可以确保请求直接经由网络,而且帮你处理了全部棘手的问题。

实时测试 service workers 提示

调试 service workers 是一件有调整性的事情,当涉及到缓存后,当你指望缓存更新,但实际上它并无的时候,事情更是变得像一场恶梦。在 service worker 典型的生命周期和你的代码之间,你很快就会受挫。但幸运的是,这里有一些工具可让你的生活更加简单。

其余的提示:

一旦 service worker 被注销(unregistered)。它会继续做用直到浏览器关闭。
若是你的应用打开了多个窗口,新的 service worker 不会工做,直到全部的窗口都进行了刷新,使用了 新的 service worker。
注销一个 service worker 不会清空缓存,因此若是缓存名没有修改,你可能继续得到到旧的数据。
若是一个 service worker 已经存在,并且另一个新的 service worker 已经注册了,这个新的 service worker 不会接管控制权,知道该页面从新刷新后,除非你使用马上控制的方式。

 

注:使用例程final或者其余service worker会出现serviceworker failed to install的错误,是由于路径缘由致使缓存文件没法加载,请修改js中的文件路径或者将images 、scripts、styles三个文件夹复制到网站根目录下。

相关文章
相关标签/搜索