写点什么

service worker 实现离线缓存

  • 2019-09-25
  • 本文字数:4348 字

    阅读完需:约 14 分钟

service worker 实现离线缓存

近两年前端比较热的话题之一就是 PWA(Progressive Web APP)——渐进式网页;致力于实现与原生 APP 相似的交互体验。


最早听说 pwa,是 2017 年年底的时候,看到朋友圈里的前端大神分享相关文章、应用。今年大概 3 月份的时候,看到 ios 从 11.3 开始支持后,就开始认真了解相关实现。


了解后发现,这一技术涉及到的内容很多。总结下来,pwa 的实现其实主要依赖于以下三点:


1)manifest 实现手机主界面的 web app 图标、标题、开屏 icon 等;


2)service worker 实现离线缓存请求、更新缓存、删除缓存;iOS safari 在 11.3 系统已支持;


3)push/notification 实现消息推送及接收。


其中 service worker 是离线应用的关键,我们主要来说说 service worker(以下简称 SW)。

1service worker 是什么

在说 SW 之前,先来回顾下 js 的单线程问题。


众所周知,js 在浏览器中是单线程运行的;主要是为了准确操作 DOM。但单线程存在的问题是,UI 线程和 js 线程需要抢占资源,在 js 执行耗时逻辑时,容易造成页面假死,用户体验较差。


由此出现了异步操作,可不影响主界面的响应。如 ajax、promise 等。


后来 html5 开放的 web worker 可以在浏览器后台挂载新线程。它无法直接操作 DOM,无法访问 window、document、parent 对象。


SW 是 web worker 的一种,也是挂载在浏览器后台运行的线程。主要用于代理网页请求,可缓存请求结果;可实现离线缓存功能。也拥有单独的作用域范围和运行环境

1.1 SW 使用限制

SW 除了 work 线程的限制外,由于可拦截页面请求,为了保证页面安全,浏览器端对 sw 的使用限制也不少。


1)无法直接操作 DOM 对象,也无法访问 window、document、parent 对象。可以访问 location、navigator;


2)可代理的页面作用域限制。默认是 sw.js 所在文件目录及子目录的请求可代理,可在注册时手动设置作用域范围;


3)必须在 https 中使用,允许在开发调试的 localhost 使用。

1.2 SW 兼容性

目前移动端 chrome 及 ios safari 11.3 以上已支持,pc 端火狐、谷歌、欧朋等支持,总体来看移动端及 pc 端都可以尝试使用。



不过在 chrome 里调试是最方便的,可以直观看到当前 SW 的状态、使用页面:



在 cacheStorage 查看缓存空间的存储信息:


1.3 service worker 可以解决的问题

1)用户多次访问网站时加快访问速度;


2)离线缓存接口请求及文件,更新、清除缓存内容;


3)可以在客户端通过 indexedDB API 保存持久化信息。

2 生命周期

生命周期有 5 步:注册、安装成功(安装失败)、激活、运行、销毁。


事件:install、activate、message、fetch、push、async。


由于是离线缓存,所以在首次注册、二次访问、服务脚本更新等会有不同的生命周期。

2.1 首次注册流程

从主线程注册后,会下载服务工作线程的 js。


安装:执行过程即是 installing 过程,此时会触发 install 事件,并执行 installEvent 的 waitUtil 方法。执行完毕后,当前状态是 installed。


激活:立即进入 activating 状态;并触发 activate 事件,处理相关事件内容。执行完成后,变成 activated 状态。


销毁: 当安装失败或进程被关闭时。


2.2 二次访问

在上一次服务未销毁时,二次访问页面,直接停留在激活运行状态:


2.3 服务脚本更新

如上图,sw.js 中,每次访问都会被下载一次。并且至少每 24 小时会被下载一次。为的是避免错误代码一直被运行。


下载后,会比对是否更新,如果更新,就会重新注册安装 serivceworker,安装成功后会处于 waiting 状态。


当 clients 都被关闭后,再次打开,才会激活最新的代码。


当然,也有方法可以跳过等待,比方说在安装完成后,执行 installEvent.skipWaiting()



3SW 的实际使用

3.1 注册

在主线程 main.js 中调起注册方法 register,register 只会被执行一次。


 1// 主线程的main.js 2// 先判断浏览器是否支持 3if('serviceWorker' in navigator){ 4  navigator.serviceWorker 5 6    // scope是自定义sw的作用域范围为根目录,默认作用域为当前sw.js所在目录的页面 7    .register('./sw.js', {scope: '/'}) 8 9    // 注册成功后会返回reg对象,指代当前服务线程实例10    .then(reg => {11      console.log('注册成功')  12    })13    .catch(error => {14      console.log('注册失败')15    })16}else{17  console.log('当前浏览器不支持SW')18}
复制代码

3.2 安装

在服务线程中添加安装监听及对应需缓存的资源文件:


 1// 在sw.js中监听对应的安装事件,并预处理需要缓存的文件 2// 该部分内容涉及到cacheStorage API 3 4// 定义缓存空间名称 5const CACHE_NAME = 'sylvia_cache' 6 7// 定义需要缓存的文件目录 8let filesToCache = [ 9  '/src/css/test.css',10  '/src/js/test.js'11]1213// 监听安装事件,返回installEvent对象14self.addEventListener('install', function(event){1516  // waitUntil方法执行缓存方法17  event.waitUntil(1819    // cacheStorage API 可直接用caches来替代20    // open方法创建/打开缓存空间,并会返回promise实例21    // then来接收返回的cache对象索引22    caches.open(CACHE_NAME).then(cache => {2324      // cache对象addAll方法解析(同fetch)并缓存所有的文件25      cache.addAll(filesToCache)26    })27  )28})
复制代码

3.3 激活

第一次注册并安装成功后,会触发 activate 事件:


1self.addEventListener('activate', event => {2  console.log('安装成功,即将监听作用域下的所有页面')3})
复制代码


在有 sw 脚本更新时,在后台默默注册安装新的脚本文件,安装成功后进入 waiting 状态。当前所有老版本控制的页面关闭后,再次打开时,新版本的脚本触发 activate 事件,此时可清除旧缓存,当前是修改 CACHE_NAME 的值来实现清除之前的缓存。


 1// 监听激活事件 2self.addEventListener('activate', event => { 3  // 获取所有的缓存key值,将需要被缓存的路径加载放到缓存空间下 4  var cacheDeletePromise = caches.keys().then(keyList => { 5    Promise.all(keyList.map(key => { 6      if(key !== CACHE_NAME){ 7        var deletePromise = caches.delete(key) 8        return deletePromise 9      }else{10        Promise.resolve()11      }12    }))13  })14  // 等待所有的缓存都被清除后,直接启动新的缓存机制15  event.waitUtil(16    Promise.all([cacheDeletePromise]).then(res => {17      this.clients.claim()18    })19  )20})
复制代码

3.4 运行

安装并激活成功后,就可以对页面实行 fetch 监控啦。


 1// 可以过滤使用已缓存的请求,若无缓存,由fetch发起新的请求,并返回给页面 2self.addEventListener('fetch', event => { 3  event.respondWith( 4    caches.match(event.request).then(res => { 5      if(res){ 6        return res 7      }else{ 8        return fetch(event.request) 9      }10    })11  )12})1314// 也可连续将请求缓存到内存里15self.addEventListener('fetch', event => {16  event.respondWith(17    caches.match(event.request).then(response => {18      if(response){19        return response20      }21      let requestClone = event.request.clone()22      return fetch(requestClone).then(res => {23        if(!res || res.status !== 200){24          return res25        }26        let resClone = res.clone()27        caches.open(CACHE_NAME).then(cache => {28          cache.put(event.request, resClone)29        })30        return res31      })32    })33  )34})
复制代码

3.5 进程销毁

1)当安装失败时会被浏览器丢弃该工作线程


2)浏览器后台启动 SW 时,会消耗性能,所以在不需要使用缓存时,可销毁


self.terminate()

4 应用框架 workbox

目前 chrome 有出一套完整的 SW 实用框架,可以较低成本的实现离线缓存


并提前封装好了对应所需的 API。

4.1 使用方法

在 sw.js 的文件中直接引入 workbox 的 cdn 上的文件:


1importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0-alpha.3/workbox-sw.js')2if(workbox){3  console.log('your workbox is working now')4}else{5  console.log('it can't work!')6}7
复制代码


如果浏览器支持,可以直接引用 API 接口:


1)precaching,可以在注册成功后直接缓存的文件;


2)routing,匹配符合规则的 url,与 strategies 合作来完成文件的缓存。


示例:


 1// 注册完成后,即缓存对应的文件列表 2workbox.precaching.precacheAndRoute([ 3  '/src/static/js/index.js', 4  '/src/static/css/index/css', 5  {url: '/src/static/img/logo.png', revision" } 6]) 7 8// routing方法匹配请求文件路径,strategies用来存储对应文件 9workbox.routing.registerRoute(10  matchFunction,  // 字符串或者是正则表达式11  handler // 可以使用workbox.strategies缓存策略来缓存12)
复制代码


workbox.strategies 缓存策略有:


1)staleWhileRevalidate 使用已有的缓存,然后发起请求,用请求结果来更新缓存;


2)networkFirst 先发起请求,请求成功后会缓存结果。如果失败,则使用最新的缓存;


3)cacheFirst 总是先使用缓存,如果无匹配的缓存,则发起网络请求并缓存结果;


4)networkOnly 强制发起请求;


5)cacheOnly 强制使用缓存。


示例:


1 // 缓存使用方法2workbox.routing.registerRoute(3  '/src/index.js',4  workbox.strategies.staleWhileRevalidate()5)
复制代码

5 总结

以上可以实现简单的离线缓存。


使用场景


1)web 页面可将较少变动的静态资源放到客户端缓存中;


2)将一些基本数据缓存后,可以让用户在无网、弱网环境下,也能正常访问页面;


3)sw 其他的类似消息推送的功能,主要用在多页面同步消息,比如:邮件、即时通讯等。


但在实际使用中,一般离线内容有: 数据请求类、静态资源类、html 页面类等。


1)数据请求类:主要可缓存一些重要数据,需要注意数据量,一般浏览器分配给 sw 的空间是有限的,需要及时更新及删除无效数据;


2)静态资源类:一般是放在 cdn 上的文件,这时需要处理一些跨域缓存问题;


3)html 页面类:页面的缓存需要谨慎处理,因为如果注册 sw 的代码写在页面上的话,注意采用第二种缓存更新方法。


缓存更新,大多是两种:


1)引用新的 sw.js 的文件,即修改注册时的文件名;


2)修改 cacheStorage 中的缓存对象名称(推荐这种)。


回到 html 的缓存问题。如果缓存了注册 sw 的页面文件,那么访问时,总会从缓存中取页面内容。此时若采用第一种缓存方式,那么会出现 sw 永远无法更新的问题。


目前来看还是比较推荐使用 workbox 来接入,实现比较方便,缓存策略也比较实用。


作者介绍:


西薇亚(企业代号名),贝壳找房租赁平台前端工程师。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/aboA9dtCK6t0fzs0JU58Iw


2019-09-25 23:482843

评论

发布
暂无评论
发现更多内容
service worker 实现离线缓存_文化 & 方法_西薇亚_InfoQ精选文章