PWA
PWA全称Progressive web apps,即渐进式 Web 应用,这个概念最开始由google推出,概念也是炒得非常火,2018年以后主流浏览器在google的推动下基本上都只支持了开发pwa的各项能力,业内也有了不少实践,在现有公司内还没有比较好的应用,于是回顾一下,看能否在现有系统中接入提升web的使用体验。
渐进式应用
什么是渐进式应用? 我的理解是,一个web应用被变得更易于发现,更易于使用,流畅性和便捷性上都得到了提升,逐渐达到接近native app的使用体验,提升用户的满意度。而官方描述为:PWA不是使用一种技术创建的。它们代表了构建Web应用程序的新理念,涉及一些特定的模式,API和其他功能。如果一个Web App从一开始就是PWA,那就不那么明显了。当应用程序满足某些要求时,可以将其视为PWA,或者实现一组给定的功能:离线工作,可安装,易于同步,可以发送推送通知等
一般认为PWA主要包含以下几种技术:
- ServiceWorker
- Fetch
- Cache API 缓存
- Push & Notification推送和通知
- Add to Home Screen,即Web App Manifest,可以直接将你的web应用添加图标到桌面上。 对于web应用来说,最大的痛点就是无法离线使用,所以PWA的核心就是利用Service worker和cache api + indexDb等实现web的离线加载和缓存
Service worker
Service worker为浏览器支持的一个特性,对应支持的浏览器版本如下:
service worker为前端开放了很多一些浏览器的底层缓存能力,类似于我们http协议中的cache机制(即304 not modified以及cache control控制的缓存能力这一套)。与http缓存不同的是,http缓存依赖于后台服务,并且无法做更加精细化的控制。 service worker让客户端完全具备控制前端请求,拦截,响应,缓存等能力。这样做离线缓存就变得更加方便。
还有一点很重要的是,service worker的设计天生就是独立于浏览器主线程,我们知道浏览器js都是运行在单线程之上,而service worker可以独立于主线程,做一些比较复杂的计算工作,再通过postMessage等方式通知主线程,以达到更好的性能。
生命周期
在网上找到一张Service worker的生命周期大概是这样的:
- Installing: service worker需要被注册,是通过调用浏览器
navigator.serviceWorker.register
API来实现,注册之后表示开始安装,触发这个生命周期,会有一些内部过程和缓存的指定,如果事件里面有event.waitUtil()则会等待promise完成才会结束。 - Installed: 表示sw已经安装完成,这个时候页面如果有之前存在的旧的sw,当前注册的sw则还不会生效,需要等待下一个步骤来激活
- Activating: 如果没有被其他sw控制的客户端,允许激活上一步注册的sw,会清理一些之前存在的缓存资源
- Activated: 激活完成后就可以使用了,sw会根据注册信息,对客户端的请求进行拦截和缓存。
- Redundant: 废弃状态,即sw不在使用,生命和周期结束
注册
首先需要一个sw.js文件,sw是核心的worker,里面定义了我们要如何来缓存资源,然后检查浏览器是否支持,如果支持即开始注册sw,在html底部添加缓存代码:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() { navigator.serviceWorker.register('./sw.js').then(function() {
// 注册成功
console.log('service worker register success');
}).catch(function(err) {
// 注册失败
console.log('service worker register failed: ', err);
});
});
}
运行后可以看到,我们的浏览已经注册成功:
sw.js
sw.js如下:定义一个CACHE_NAME用于记录缓存标识cacheUrl来定义需要缓存的url目录
var CACHE_NAME = 'my-sw-cache';
var cacheUrl = [
'/',
'/js/app.js',
'/chunk-vendors.js'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
// console.log('Opened cache');
return cache.addAll(cacheUrl);
})
);
});
打开浏览器可以看到,由sw.js发起的几个缓存文件
在application界面可以看到注册sw文件:
到这里基本的搭建就完成了,后续主要是利用api 对缓存进行详细的控制,例如直接拦截响应
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 如果发现匹配的响应,则返回缓存的值
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
对于上面这些其实只是最基本的搭建,对于缓存策略的更新& 卸载,路由处理,结合webpack对于动态hash文件的缓存等等都有不同的处理方式,但是service worker开放的缓存能力,为前端缓存提供了更丰富的想象力。