1 什么是pwa?
PWA全称为ProgressiveWeb Apps(渐进式网页应用),是由谷歌提出推广的,在移动端利用提供的标准化框架,使得网页可以像原生App一样运作。在离线监控中,我们目前只需要用到ServiceWorker的离线功能。
2 什么是ServiceWorker?
ServiceWorker是一个浏览器和network之间的代理,独网页进程,有自己独立的worker context,可以拦截页面里的fetch请求,同时对于需求可以对依赖的文件进行缓存。
一个完整的serviceworker生命周期需要经过三步:
- 注册 Registration
- 安装 Installation
- 激活 Activation
2.1 注册
注册代码可以放在script标签里面,告诉浏览器我们的service worker是哪个文件,然后在后台service worker开始安装。现在各浏览器支持程度不同,所以需要先判断浏览器是否支持它。
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').then((registration) => {
console.log('Service Worker Registration++++++')
console.log('registration: ', registration)
}, (err) => {
console.log(err)
})
})
}
代码检测是否支持,然后在window的load完成事件里注册sw.js文件,通过registration参数看到对应的情况。里面有scope参数,表明当前service worker的作用域。我们也可以在注册的时候通过config参数来指定作用域。
navigator.serviceWorker.register('/sw.js', {
scope: '/app/'
});
注意:sw.js最好放在根路径下,参考上面的方式,下面这种引入是错误的
navigator.serviceWorker.register('/js/sw.js', {
scope: '/app/'
});
代码中指的作用域是app路径下所以的页面都会生效,比如/app/index、/app/home等的路径地址。
如果service worker已经安装过了,再次安装的话会返回当前活动的注册对象。
2.2 安装
我们在sw.js文件里进行安装,安装成功之后install事件就会被触发,我们可以在里面向caches里进行缓存文件、数据等操作。
var cacheName = 'my-cache'
var cacheList = ['/cache1.css', '/cache1.js']
self.addEventListener('install', function(event) {
event.waitUntil(
// 安装成功后向caches中存入需要缓存的文件
caches.open(cacheName).then(function (cache) {
console.log('cach2: ', cache)
for (let key in cache) {
console.log(`${key}: ${cache[key]}`)
}
return cache.addAll(cacheList)
})
)
});
通过caches.open()方法打开对应的缓存文件,通过参数里面的addAll方法添加缓存列表。当安装完成时,即service worker就会激活成功
2.3 激活
当安装完成即激活成功,进入激活状态。
3 service worker如何进行更新操作?
当页面缓存静态文件成功之后,如何更新文件呢?sw.js会控制缓存的文件,当有文件更新的时候,sw.js会逐字节的对比新旧文件,如果发现文件有更新,它会启动更新算法,于是会注册安装新的service worker,旧的service worker还会继续功能,新的service worker会进入一个等待期,直到当页面全部关闭的时候,新的worker会替换掉旧的worker,进行更新操作。
如果我们希望页面可以及时更新,应该怎么办呢?
service worker里面提供了一个skipWaiting的事件,我们可以在install事件里,直接执行self.skipWaiting()方法,让新的service worker跳过waiting等待期,直接进入激活状态。
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});
我们也可以通过手动来更新缓存的文件
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.update();
});
4 另一个缓存
上面我们说过了service worker可以缓存文件,那是否也可以缓存业务数据呢?答案是可以的,我们可以在sw.js里来监听window上的fetch请求,来监听请求收集请求数据。
// 监听service worker fetch
self.addEventListener('fetch', function (event) {
event.respondWith(
// 匹配缓存
caches.match(event.request)
.then(function(response) {
console.log('sw response: ', response)
// 在缓存中查找到匹配的请求,就从缓存返回
if (response) {
console.log(response)
return response;
}
// 缓存中没有查找到对应请求,继续网络请求
return fetch(event.request);
}
)
);
})
在这里我们可以收集fetch的request作为key来收集response,其中request里面会有完整的请求。接着我们对fetch请求进行接管,之后就可以看到network里的请求都经过service worker。
所有访问过的页面都会被缓存并允许在离线环境下继续访问,所有未访问过的页面则会在离线环境下展示一个自定义的离线页面
5 pwa缓存与http缓存的区别
Service worker除了针对pwa单独支持的功能外,对普通web来说,在缓存方面能比http缓存带来一些额外的好处,可以理解位sw就是浏览器把缓存管理开放一层接口给开发者。
1、浏览器默认在刷新时,会对所有资源都重新发起请求,即使缓存还在有效期内,而使用sw就可以改写这个行为直接返回缓存。
2、要让网页离线使用就需要整站使用长缓存,包括html。而html使用了长缓存,就无法即使更新。而使用sw就可以,每次优先使用缓存部分,然后再次发起sw的请求,这个请求我们可以实施变更,修改html版本,重新缓存一份,那么下次用户打开的时候就可以看到新版本了。
3、http缓存空间有限,容易被冲掉。虽然部分浏览器实现sw的缓存也有淘汰机制,但多一层缓存,命中的概率就要更高了。
4、当检测到离线的时候,而且又没有缓存某个图片时,可以做特殊处理,返回离线的提示;又或者做一个纯前端的404页面等。
总结:传统的http缓存有它自己的缓存策略,也就是浏览器缓存,但是它的本质是由服务端控制的,通过约定有效时长、缓存策略以及资源有效期等信息来达到缓存的目的,而serviceworker则通过管理caches文件,缓存cache资源来实现离线化,serviceworker的出现并不是单纯的解决细化浏览器缓存问题,它能充当代理服务器这一能力(拦截请求来实现),能够实现浏览器缓存无法实现的离线功能。
所以基于应用及用户体验,在某一方面serviceworker可以带来比http缓存更优的用户体验,同时缺点也是不会达到像浏览器那样可以达到精细化控制缓存的效果
6 配置文件mainfest.json
Pwa还有另一个可配置的文件是mainfest,它给予开发者可定义自定义图标、名称、启动方式等信息添加到桌面的能力,让用户方便快捷地将启动站点添加到主屏幕中。
<!--通过html的script标签将其引入到文件中-->
<link rel="manifest" href="path-to-manifest/manifest.json">
打开manifest.json文件,里面做了配置介绍:
{
/* 自定义名称 */
"short_name": "简称",
"name": "完整名称",
/** 自定义安装 icon
* 当PWA添加到主屏幕时,浏览器会根据有效图标的 sizes 字段进行选择。
* 首先寻找与显示密度相匹配并且尺寸调整到 48dp 屏幕密度的图标;
* 如果未找到任何图标,则会查找与设备特性匹配度最高的图标;
* 如果匹配到的图标路径错误,将会显示浏览器默认 icon。
*
* 在启动应用时,启动画面图像会从图标列表中提取最接近 128dp 的图标进行显示
*/
"icons": [
{
"src": "path-to-images/icon-96x96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "path-to-images/icon-144x144.png",
"type": "image/png",
"sizes": "144x144"
}
],
/*
定义了 web 应用的浏览作用域,比如作用域外的 URL 就会打开浏览器而不会在当前 PWA 里继续浏览
*/
"scope": "/sample/",
/* 设置启动网址 */
"start_url": "index.html",
/** 设置启动背景颜色
* 完整色值 "#0000ff"
* 缩写 "#00f"
* 预设色值 "blue"
* rgb "rgb(0, 0, 255)"
* transparent 背景色显示为黑色
*/
"background_color": "#0000ff",
/** 设置启动显示类型
* fullscreen 应用的显示界面将占满整个屏幕
* standalone 浏览器相关UI(如导航栏、工具栏等)将会被隐藏
* minimal-ui 显示形式与standalone类似,浏览器相关UI会最小化为一个按钮,不同浏览器在实现上略有不同
* browser 浏览器模式,与普通网页在浏览器中打开的显示一致
*/
"display": "fullscreen",
/** 指定页面显示方向
* 更多配置介绍:https://lavas.baidu.com/pwa/engage-retain-users/add-to-home-screen/improved-webapp-experience#%E6%8C%87%E5%AE%9A%E9%A1%B5%E9%9D%A2%E6%98%BE%E7%A4%BA%E6%96%B9%E5%90%91
*/
"orientation": "landscape",
/* 设置主题颜色 */
"theme_color": "#000",
/** 设置作用域
* start_url 必须在作用域内
*/
"scope": "/"
}
7 开发pwa遇到的坑及注意事项
1、首先pwa只能支持在https环境下使用,它是不支持http的,所以会出现在https域名下
如果夹杂着http环境的资源,会加载失败。
2、在ServiceWorker中如果想要拦截请求的话,不能拦截ajax请求,它只支持拦截
fetch请求,所以这一点也比较坑,对业务的限制会比较多。
3、iOS safari不支持manifest配置来实现添加到桌面,但是可以通过safari自有的meta标签来实现standalone模式,不过问题就出在了standalone模式上。抛开iOS safari standalone模式现有的一些其他小bug(包括状态栏的显示、白屏、重复添加等)
注:pwa实际开发中涉及到的问题还有很多,我这里只罗列了一小部分的坑和注意事项,具体表现还是需要根据
自己的项目来尝试体验,最终决定是否采用pwa来替代页面应用。
从最新的支持情况看来,除了ie外,大部分主流的浏览器已经开始可以很好的支持service worker。相信在不远的未来,pwa就可以很广泛的应用到实际的项目中。
pwa目前是前端比较热门的一种技术,它提升了用户体验,并且可以达到几乎匹配app原生的体验,但目前它的主要难题在支持程度上,如果支持程度没有问题,相信它将很快被大面积推广到实际应用中。
8 参考资料
pwa支持查询:https://caniuse.com/?search=pwa
https://huangxuan.me/2017/02/09/nextgen-web-pwa/
https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/caches