service worker 对静态资源进行缓存

4,103 阅读4分钟

service worker

HTML5标准中引入了webworker,它是运行在后台的JavaScript,独立于其他脚本,不会影响页面的性能。Service Worker就是Web Worker的一种实现,充当Web应用程序的本地代理服务器;在特定路径注册Service Worker后,我们可以拦截并处理该路径下的所有网络请求;本文中,我们就是借助于这种代理机制,实现对web页面核心资源可编程式缓存。

基本用法

1. 注册

navigator.serviceWorker.register('your_service_worker.js',{scope: 'your-path-name'}).then(function (registration) {
    console.log('succeeded');
}).catch(function (error) {
    console.log('error' + error);
});

register 方法接受两个参数,第一个是service worker 的文件路径,第二个是 service worker 的配置项,其中scope 属性控制的是service worker 工作目录,默认为 service worker 所在文件目录。在同一个Origin 下可以注册多个Service Worker,但是Scope不能相同。

2. 注销

navigator.serviceWorker.getRegistration('your_service_worker.js',{scope: 'your-path-name'}).then(function (registration) {
    if (registration && registration.unregister) {
        registration.unregister().then(function (isUnRegistered) {
            if (isUnRegistered) {
                console.log('unRegistration  succeeded.');
            } else {
                console.log('unRegistration failed.');
            }
        });
    }
).catch(function (error) {
    console.log('[SW]: UnRegistration failed with. ' + error);
});

首先需要通过getRegistration获取service woker,然后调用service woker的unregister 方法进行注销。

3.事件监听

// 安装监听
this.addEventListener('install', function (event) {
  console.log('Service Worker install');
});

// 激活监听
this.addEventListener('activate', function (event) {
  console.log('Service Worker activate');
});

生命周期

生命周期

Installing

注册完 service worker 后,浏览器会下载并解析,默认情况下,service worker会24小时被下载一次。

Activating & Activated

在脚本被安装完成后,service worker 会 依次进入 Activating 和 Activated 状态。失败则会进入Redundant 状态。

Redundant

在 Installing 或 Activating 失败后,会进入此状态,在旧 service worker 被新 service Worker 取代后,也会进入此状态。

通信

从页面到service worker

if ('serviceWorker' in window.navigator) {
  navigator.serviceWorker.register('your_service_worker.js', { scope:'your-path-name' })
    .then(function (reg) {
      navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("hello");
    });
}

从 service worker 到页面

首先监听,来自页面的消息,根据页面的消息得到event.source 进行相应。

this.addEventListener('message', function (event) {
  event.source.postMessage('this message is from sw.js, to page');
});

静态资源缓存

终于到了本文的重点了,在Service worker中可以缓存的包括css、js、图片等在内的几乎全部静态资源。使用service Worker 缓存资源的方式一般为

1.service worker安装成功后开始缓存所需的资源
this.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('my_sw_cache').then(function (cache) {
      return cache.addAll([
        '/',
        '/script/style.css',
      ])
    }
    ));
});

使用 caches.open 方法新建或打开一个已存在的缓存,cache.addAll方法是请求指定连接的资源并把他们存储到这个缓存中,而使用event.waitUntil能保证资源被缓存完成前 Service Worker 不会被安装完成,避免发生错误。

2.监听浏览器的所有fetch请求,对已经缓存的资源使用本地缓存进行返回。
this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        //该fetch请求已经缓存
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});

3. 版本控制

前面说到,有新的 service worker 后,旧版本的页面会被全部关闭,此时就需要我们清理旧的缓存了,如何识别并找出旧版本的缓存就是重点了。而cacheStorage 提供了简单的API,便于我们能查找出旧的缓存资源。其中CACHE_PREFIX是应用的cache前缀,CACHE_VERSION是本次cache的版本号。

function deleteCache() {
    return caches.keys().then(function (keys) {
        var all = keys.map(function (key) {
            if (key.indexOf(CACHE_PREFIX) !== -1 && key.indexOf(CACHE_VERSION) === -1){
                  console.log('Delete success-->' + key);
                  return caches.delete(key);
            }
        });
        return Promise.all(all);
    });
}

白名单控制

并不是所有的旧的缓存都不需要,有些缓存可以一直使用,所以需要设置一个白名单,当Service Worker 被激活的时候,将不在白名单中的缓存删掉。

const noDelete = ['you_no_delete_source.js'] // 白名单
function deleteCache() {
    return caches.keys().then(function (keys) {
        var all = keys.map(function (key) {
            if (key.indexOf(CACHE_PREFIX) !== -1 && key.indexOf(CACHE_VERSION) === -1 && !noDelete.includes(key)){
                  console.log('Delete success-->' + key);
                  return caches.delete(key);
            }
        });
        return Promise.all(all);
    });
}

总结

上述代码只是实现了一个demo,实际情况可能复杂多了,比如

1.哪些资源需要缓存,并不是人为控制的,更多情况是webpack打包生成的文件,所以需要生成一套正则匹配规则,用于识别不同的文件,哪些需要缓存,缓存多久。

2.缓存控制不得当,可能获得灾难性后果,若用户得不到正确的相应结果,可能造成页面无响应,数据错乱各种错误。所以需要对响应错误码做额外处理,如避免缓存304,404,xx等不需要缓存的内容。