service worker的使用

487 阅读3分钟

service worker的工作机制

1.首先需要加载和安装sw.js

window.onload = () => {
    if (navigator.serviceWorker) {  
      navigator.serviceWorker.register('/sw.js',{ scope: '/js' })
        .then(registration => {
          console.log('恭喜。作用范围: ', registration.scope);
        })
        .catch(error => {
          console.log('抱歉', error);
        });
    }
}
  • 在onload之后触发是为了页面加载而做的性能优化,因为serviceWorker也需要使用到cpu和内存,所以延迟安装sw.js。
  • scope:指定serviceWorker作用的路径,可以理解为serviceWorker的作用域。
  1. service worker的生命钩子
  • 1: parsed: 注册完成, 脚本解析成功, 尚未安装

  • 2: Install(waiting): 首次安装成功事件或新版本serviceWorker等待安装事件

const CACHE_NAME = 'cache-v1';  
const urlsToCache = [  
  '/',
  '/js/main.js',
  '/css/style.css',
  '/img/bob-ross.jpg',
];

self.addEventListener('install', event => {  
    self.skipWaiting() // 跳过等待
    caches.open(CACHE_NAME)
    .then(cache => {
        return cache.addAll(urlsToCache);
    });
});
  • CACHE_NAME:指定service worker中cache storage的名称
  • urlsToCache:初始化页面需要缓存的资源列表,若为空数组则初始化不缓存资源
  • 当service worker版本存在交替(存在新版本的service worker:既sw.js发生变化时),新版本service worker需要等待(waiting),跳过等待后才能触发新版本的activate。跳过等待需要满足以下任意条件
  • 1: 关闭所有往前网站的浏览器tab,在重新打开当前网站,会触发新版本service worker的activate事件
  • 2: 在activate事件的回调函数中直接调用self.skipWaiting(): 可直接触发新版本service worker的activate事件。
  • 3: Activate: 事件
  • Activate事件再首次安装service worker或新版本service worker安装完成后触发
self.addEventListener('activate'function(event) { 
    self.clients.claim()
    event.waitUntil(
        // 获得缓存中所有键
        caches.keys().then(function(cacheNames) {
            return Promise.all(
            // 遍历所有的缓存文件
            cacheNames.map(function(cacheName) {
            // 若缓存文件不在白名单中,删除之
              if (cacheName === CACHE_NAME) {
                  return caches.delete(cacheName);
              }
            })
          );
        })
      );
});
  • 当 Service Worker 被首次注册时,已打开的页面只有在刷新后才会接受 Service Worker 的控制,如果想要 Service Worker 在激活后尽快掌握这些页面的控制权,可在 activate 中调用 self.clients.claim 方法来实现
  • 4: activated: 激活成功, 可以处理 fetch, message 等事件
  1. service worker中可以绑定的事件
  • install 事件中, 抓取资源进行缓存(在初始化时根据指定的urlsToCache缓存资源)
  • 通过前面的urlsToCache指定初始化缓存的资源,urlsToCache为空数组则初始化不缓存任何资源。
  • activate 事件中, 遍历缓存, 清除过期的资源
// 通过event.waitUntil删除所有过期的资源
 event.waitUntil(
        // 获得缓存中所有键
        caches.keys().then(function(cacheNames) {
            return Promise.all(
            // 遍历所有的缓存文件
            cacheNames.map(function(cacheName) {
            // 若缓存文件不在白名单中,删除之
              if (cacheName === CACHE_NAME) {
                  return caches.delete(cacheName);
              }
            })
          );
        })
      );
  • fetch 事件中, 拦截请求, 查询缓存或者网络, 返回和缓存请求的资源。(在请求时根据代码条件进行缓存)
// 通过fetch事件获取和缓存所有需要缓存的资源。
self.addEventListener('fetch', function(event) {
    // 只对 get 类型的请求进行拦截处理
    if (event.request.method !== 'GET') {
        console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
        return;
    }
    event.respondWith(
        // 缓存中匹配请求
        caches.match(event.request)
          .then(function(response) {
            if (response) {
              return response;
            }

            // 因为 event.request 流已经在 caches.match 中使用过一次,
            // 那么该流是不能再次使用的。我们只能得到它的副本,拿去使用。
            var fetchRequest = event.request.clone();

            // fetch 的通过信方式,得到 Request 对象,然后发送请求
            return fetch(fetchRequest).then(
              function(response) {
                // 检查是否成功
                    if(!response || response.status !== 200 || response.type !== 'basic') {
                  return response;
                }

                // 如果成功,该 response 一是要拿给浏览器渲染,而是要进行缓存。
                // 不过需要记住,由于 caches.put 使用的是文件的响应流,一旦使用,
                // 那么返回的 response 就无法访问造成失败,所以,这里需要复制一份。
                var responseToCache = response.clone();

                caches.open(CACHE_NAME)
                  .then(function(cache) {
                    cache.put(event.request, responseToCache);
                  });

                return response;
              }
            );
          })
        );
});

方式1:基于上面的service woker机制搭建service worker缓存。

  • 注意:不要缓存/sw.js,这样会导致/sw.js无法更新。整个service worker的版本就无法更新。

解决方案:service worker不要缓存index.html,每次构建时给/sw.${version}js加版本号。在index.html的navigator.serviceWorker.register方法中注入新版本的/sw.${version}js地址。就能及时触发serviceWorker更新。

方式1:基于webpack插件workbox-webpack-plugin

  • 具体使用查看workbox-webpack-plugin文档。