近两年前端比较热的话题之一就是 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 只会被执行一次。
3.2 安装
在服务线程中添加安装监听及对应需缓存的资源文件:
3.3 激活
第一次注册并安装成功后,会触发 activate 事件:
在有 sw 脚本更新时,在后台默默注册安装新的脚本文件,安装成功后进入 waiting 状态。当前所有老版本控制的页面关闭后,再次打开时,新版本的脚本触发 activate 事件,此时可清除旧缓存,当前是修改 CACHE_NAME 的值来实现清除之前的缓存。
3.4 运行
安装并激活成功后,就可以对页面实行 fetch 监控啦。
3.5 进程销毁
1)当安装失败时会被浏览器丢弃该工作线程
2)浏览器后台启动 SW 时,会消耗性能,所以在不需要使用缓存时,可销毁
self.terminate()
4 应用框架 workbox
目前 chrome 有出一套完整的 SW 实用框架,可以较低成本的实现离线缓存
并提前封装好了对应所需的 API。
4.1 使用方法
在 sw.js 的文件中直接引入 workbox 的 cdn 上的文件:
如果浏览器支持,可以直接引用 API 接口:
1)precaching,可以在注册成功后直接缓存的文件;
2)routing,匹配符合规则的 url,与 strategies 合作来完成文件的缓存。
示例:
workbox.strategies 缓存策略有:
1)staleWhileRevalidate 使用已有的缓存,然后发起请求,用请求结果来更新缓存;
2)networkFirst 先发起请求,请求成功后会缓存结果。如果失败,则使用最新的缓存;
3)cacheFirst 总是先使用缓存,如果无匹配的缓存,则发起网络请求并缓存结果;
4)networkOnly 强制发起请求;
5)cacheOnly 强制使用缓存。
示例:
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
评论