欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ServiceWorker离线缓存教程

程序员文章站 2022-10-31 16:07:28
pwa资料整理(二):service worker 离线缓存 pwa资料整理(二):service worker 离线缓存 本篇是 pwa 资料整理的第二篇,主要介绍 service worker...

    pwa资料整理(二):service worker 离线缓存

    pwa资料整理(二):service worker 离线缓存

    本篇是 pwa 资料整理的第二篇,主要介绍 service worker 所实现的离线缓存相关内容。

    系列链接

    manifest 添加到桌面 service worker 离线缓存(本篇) service worker 消息推送

    html5 manifest

    说到离线缓存,那就不得不提到之前 w3c 标准的 html5 manifest 的 app cache 离线缓存了。虽然是官方标准,但是一直都没什么热度,大家都不爱用,因此也没去尝试。
    html5 manifest 几个注意(坑)点大概如下:

    站点离线存储的容量限制是5m 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,继续全部使用老的缓存 引用manifest的html必须与manifest文件同源,在同一个域下 在manifest中使用的相对路径,相对参照物为manifest文件 cache manifest字符串应在第一行,且必不可少 会自动缓存引用清单文件的 html 文件 manifest文件中cache则与network,fallback的位置顺序没有关系,如果是隐式声明需要在最前面 fallback中的资源必须和manifest文件同源 当一个资源被缓存后,该浏览器直接请求这个绝对路径也会访问缓存中的资源。 站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问 当manifest文件发生改变时,资源请求本身也会触发更新

    service worker

    service worker 可能是 pwa 相关技术中最重要的一个了,其可以充当web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。sw 能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。
    除了提供离线缓存的能力以外,sw 能够做的事情还很多,如:

    预取用户可能需要的资源; 如 web worker 一样,负责处理密集的 cpu 计算,如地理位置和陀螺仪信息的计算; 后台数据同步; 消息推送; etc。

    sw 的特点是套路非常固定,无侵入,直接复制粘贴就可以实现缓存和离线功能,纯前端,无需服务器配合。

    service worker 生命周期

    两张(偷来的)图,分别是 service worker 自身线程的生命周期:
    ServiceWorker离线缓存教程
    以及service worker 线程与渲染主线程的交互过程:
    ServiceWorker离线缓存教程

    sw 线程的生命周期包含安装、激活、等待、销毁四个阶段,具体如下:

    渲染主线程执行script中代码,其包含了对 sw api 的调用,并将用户编写的 sw.js 文件进行注册/parse/installing; 主线程installing会触发 sw.js 中监听的 install 事件。使用cache api来将资源缓存起来,同时使用e.waituntil接收一个promise来等待资源缓存成功,等到这个promise状态成功后,serviceworker进入installed状态,意味着安装完毕; 安装结束后当新的sw使用了self.skipwaiting()或者旧的的sw释放/过期了之后,主线程进入activating状态,sw线程触发到active事件。这个时候通常做一些缓存清理工作,当e.waituntil接收的promise进入成功状态后,serviceworker的生命周期则进入activated状态。注意:如果你的页面加载时没有 service worker,那么它当前加载过程中所依赖的其他资源请求也不会触发 fetch 事件。也就是说第一次加载不会触发 fetch 事件; 到此一个serviceworker正式进入激活状态,可以拦截网络请求了。如果主线程有fetch方式请求资源,那么就可以在serviceworker代码中触发fetch事件; 那么如果在install或者active事件中失败,serviceworker则会直接进入redundant状态,浏览器会释放资源销毁serviceworker。

    service worker 使用注意

    与 web worker 不同,service worker 不是服务于某个特定页面的,而是服务于多个页面的(按照同源策略); service worker 会常驻在浏览器中,即便注册它的页面已经关闭,service worker 也不会停止。本质上它是一个后台线程,只有你主动终结,或者浏览器回收,这个线程才会结束; 生命周期、可调用的 api 等等也有很大的不同; 当有任何的资源(html、js、image、甚至是 sw.js 本身)需要更新时,都需要改变 sw.js。因为有了 sw.js,整个应用的入口变成了 sw.js,而非原先的 html。每当用户访问页面时,不管你当前是不是命中了缓存,浏览器都会请求 sw.js,然后将新旧 sw.js 进行字节对比,如果不一样,说明需要更新; 出于安全考量,service worker 必须使用 https,因为允许截获用户请求的能力可能会被中间人攻击所利用。

    cache

    service worker 本质上提供了类似 web worker 的功能,其作为 web application 以及 server 之间的代理服务器,可以截获用户的请求。但是为了实现离线缓存功能,还需要结合 cache api。

    使用 cache storage 还需要注意以下几点:

    它只能缓存 get 请求; 每个站点只能缓存属于自己域下的请求,同时也能缓存跨域的请求,比如 cdn,不过无法对跨域请求的请求头和内容进行修改 缓存的更新需要自行实现; 缓存不会过期,除非将缓存删除,而浏览器对每个网站 cache storage 的大小有硬性的限制,所以需要清理不必要的缓存。

    这里是一个来自 mdn 的 cache api 与 service worker 结合的示例,其展示了对指定的 font 文件进行缓存的一个最佳实践,同理也可以拓展到多个缓存对象的情况。代码如下:

    var cache_version = 1;
    
    // shorthand identifier mapped to specific versioned cache.
    var current_caches = {
      font: 'font-cache-v' + cache_version
    };
    
    self.addeventlistener('activate', function(event) {
      var expectedcachenames = object.keys(current_caches).map(function(key) {
        return current_caches[key];
      });
    
      // active worker won't be treated as activated until promise resolves successfully.
      event.waituntil(
        caches.keys().then(function(cachenames) {
          return promise.all(
            cachenames.map(function(cachename) {
              if (expectedcachenames.indexof(cachename) == -1) {
                console.log('deleting out of date cache:', cachename);
                
                return caches.delete(cachename);
              }
            })
          );
        })
      );
    });
    
    self.addeventlistener('fetch', function(event) {
      console.log('handling fetch event for', event.request.url);
    
      event.respondwith(
        
        // opens cache objects that start with 'font'.
        caches.open(current_caches['font']).then(function(cache) {
          return cache.match(event.request).then(function(response) {
            if (response) {
              console.log(' found response in cache:', response);
    
              return response;
            } 
          }).catch(function(error) {
            
            // handles exceptions that arise from match() or fetch().
            console.error('  error in fetch handler:', error);
    
            throw error;
          });
        })
      );
    });