service worker打工仔

629 阅读7分钟

先说PWA

  1. 概念:渐进式 Web 应用会在桌面和移动设备上提供可安装的、仿应用的体验
  2. 实现条件
    • 使用响应式设计,因为它将可用在桌面或移动设备上
    • 快速,使用service worker****来预缓存运行时所需要的应用资源(html/css/js/图像)
    • 可安装,使用web应用清单(manifest)和beforeinstallprompt事件告知和通知用户它是可安装的

再说service worker

service worker原理

service worker是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。现在,他们已提供包括推送通知、后台同步的功能。将来,还会支持如定期同步或者地理围栏等其他功能。
我们本次研究的是拦截和处理网络请求,包括通过它来管理缓存中的响应。
因为上述功能,它使得web app可以支持离线体验。
service worker相关注意事项:

  • 它是一种 JavaScript Worker,无法直接访问 DOM。 Service Worker 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
  • Service Worker 是一种可编程网络代理,让您能够控制页面所发送网络请求的处理方式。
  • Service Worker 在不用时会被中止,并在下次有需要时重启,因此,您不能依赖 Service Worker onfetchonmessage 处理程序中的全局状态。 如果存在您需要持续保存并在重启后加以重用的信息,Service Worker 可以访问 IndexedDB API
  • Service Worker 广泛地利用了 promise
  • 在开发过程中,可以通过 localhost 使用 Service Worker,但如果要在网站上部署 Service Worker,则需要在服务器上设置 HTTPS

service worker生命周期

要为网站安装service worker,你需要先在页面的js中进行注册。注册service worker将会使浏览器在后台启动service worker安装步骤。
在安装过程中,我们通常需要缓存某些静态文件。如果静态文件均已被缓存成功,那么service worker就安装完毕,等待被激活;否则则安装失败,service worker将无法激活(如果发生这种情况,它下次会重试)。
激活之后,Service Worker 将会对其作用域内的所有页面实施控制,不过,首次注册该 Service Worker 的页面需要再次加载才会受其控制。 service worker实施控制后,它将处于以下两种状态之一:服务工作线程终止以节省内存,或处理获取和消息事件,从页面发出网络请求(fetch)或消息(postMessage)后将会出现后一种状态。

image.png

image.png

service worker实际应用

1. 注册service worker

使用api:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

2. 监听自身的install事件, 添加需要缓存的文件

使用api:

  • event.waitUntil, 作为extendableEvent的一个api,ExtendableEvent是service worker生命周期的一部分,可以延长在全局范围上installactivate事件的生命周期,确保在删除过时的caches之前,不会调度任何函数事件。
  • caches.open(CACHE_NAME), 表示Cache对象的存储,在全局可以访问。通过caches.open方法能获取到Cache实例,同样是通过promise方式返回。
  • cache.addAll(urlsToCache), cache为缓存的Request/Response对象提供存储机制,addAll抓取一个url数组,检索并把返回的response对象添加到给定的cache对象里。

以上,当install事件发生时,service worker会开始抓取需要缓存的文件并将其添加到对应的cache对象里。缓存结束则该install事件结束。

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

3. 监听fetch事件, 从缓存中返回请求内容

situation: 当用户转至其他页面或刷新当前页面后,service worker将开始接收fetch事件(刷新或者转至其他页面时,用户利用fetch请求静态资源)


使用api:

  • event.respondWith, 为fetch event的api,阻止浏览器的默认fetch handler,允许用户自行提供一个promise作为fetch事件的响应。
  • caches.match,检查给定的Request是否是CacheStorage对象跟踪的任何Cache对象的键,并返回一个resolve为该匹配的promise.
  • caches.open, 通过caches.open方法获取到对应cacheName的Cache实例。
  • cache.put,同时抓取一个请求及其响应,并将其添加到给定的cache。

详细说明:service worker监听到fetch事件以后,检查当前request是否是CacheStorage对象跟踪的任何Cache对象的键,是的话返回匹配到的请求的响应,如果未找到响应的话,则重新fetch当前的request,并且在fetch到后将其响应添加到cache实例中。

self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request)
        .then(function(response) {
            if (response) {
                return response;
            }
            var requestToCache = event.request.clone();
            return fetch(requestToCache).then(function(response) {
                if (!response || response.status !== 200) {
                    return response;
                }
                var responseToCache = response.clone();
                caches.open(cacheName).then(function(cache) {
                    cache.put(requestToCache, responseToCache);
                });
                return response;
            })
        })
    )
})

4. 更新 service worker

situation: 在某个时间点,您的 Service Worker 需要更新。 此时,您需要遵循以下步骤:

  1. 更新您的服务工作线程 JavaScript 文件。 用户导航至您的站点时,浏览器会尝试在后台重新下载定义 Service Worker 的脚本文件。 如果 Service Worker 文件与其当前所用文件存在字节差异,则将其视为_新 Service_ Worker。
  2. 新 Service Worker 将会启动,且将会触发 install 事件。
  3. 此时,旧 Service Worker 仍控制着当前页面,因此新 Service Worker 将进入 waiting 状态。
  4. 当网站上当前打开的页面关闭时,旧 Service Worker 将会被终止,新 Service Worker 将会取得控制权。
  5. 新 Service Worker 取得控制权后,将会触发其 activate 事件。

我们希望在 activate 回调中执行此任务的原因在于,如果在安装步骤中清除了任何旧缓存,则继续控制所有当前页面的任何旧 Service Worker 将突然无法从缓存中提供文件。


使用api:

  • event.waitUntil, 作为extendableEvent的一个api,ExtendableEvent是service worker生命周期的一部分,可以延长在全局范围上installactivate事件的生命周期,确保在删除过时的caches之前,不会调度任何函数事件。
  • caches.keys,使用该方法迭代所有 Cache 对象的列表
  • caches.delete,查找匹配 cacheName 的 Cache 对象,如果找到,则删除 Cache 对象并返回一个resolve为true的 Promise 。如果没有找到 Cache 对象,则返回 false.


详细说明:service worker被激活后,检查当前的所有Cache对象,如果并不在此service worker的缓存列表中,则认为是老旧版本的Cache对象并将其从cacheStorage中删除。

self.addEventListener('activate', function(event) {

  var cacheAllowlist = ['pages-cache-v1', 'blog-posts-cache-v1'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheAllowlist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

动手实践

1. 启动文件中引入并注册

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- manifest文件可以自己看着配哦 -->
    <link rel="manifest" href="./manifest.json">
    <!-- 这是我要缓存的css -->
    <link rel="stylesheet" type="text/css" href="/cache1.css">
    <title>pwa</title>
</head>
<body>
    <div id="app" class="test">test1</div>
    <!-- 这是我要缓存的js -->
    <script src='/cache1.js'></script>
    <script>
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function() {
                navigator.serviceWorker.register('/sw.js').then((registration) => {
                    console.log('Service worker registration', registration);
                }, (err) => {
                    console.log(err);
                })
            })
        }
    </script>
</body>
</html>

2. service worker文件大概长这样

const cacheName = 'my-cache';
const cacheList = ['cache1.css', 'cache1.js'];

self.addEventListener('install', function(event) {
    console.log('installing', caches, event);
    event.waitUntil(
        caches.open(cacheName).then(function(cache) {
            console.log('opening', cache);
            // 抓取url数组, 检索并把最终返回的response对象添加到cache对象
            return cache.addAll(cacheList);
        })
    )
})

self.addEventListener('fetch', function(event) {
    console.log('fetch', caches.keys());
    event.respondWith(
        caches.match(event.request)
        .then(function(response) {
            if (response) {
                return response;
            }
            var requestToCache = event.request.clone();
            return fetch(requestToCache).then(function(response) {
                if (!response || response.status !== 200) {
                    return response;
                }
                var responseToCache = response.clone();
                caches.open(cacheName).then(function(cache) {
                    cache.put(requestToCache, responseToCache);
                });
                return response;
            })
        })
    )
})

3. 最终效果是……

image.png
image.png
没错,他被激活了, 手动刷新一下发现
image.png
你也可以勾上offline试试看
image.png

4. 时间对比


当我没有用service worker
image.png

当我有了service worker
image.png

更多

1. vue-cli 3中的pwa使用的google团队提供的worbox webpack plugin

2. service worker还具备推送消息的功能


参考文章:
你的第一个pwa应用:developers.google.com/web/fundame…
service worker基础使用:googlechrome.github.io/samples/ser…
web worker使用:www.html5rocks.com/en/tutorial…
service worker 推送消息:juejin.cn/post/684490…
@vue/cli-plugin-pwa: github.com/vuejs/vue-d…