当前web应用在移动时代并没有达到其在桌面设备上流行的程度,下面有张图来对比与原生应用之间的差别。
究其原因,无外乎下面不可避免的几点:
假如能解决以上的几点,对web app 来说会有多大的提升可以想象。
PWA 全称Progressive Web Apps(渐进式Web应用程序),旨在使用现有的web技术提供用户更优的使用体验。
基本要求
PWA 本身强调渐进式,并不要求一次性达到安全、性能和体验上的所有要求,开发者可以通过 PWA Checklist 查看现有的特征。
除以上的基准要求外,还应该包括以下特性:
看起来有点眼花缭乱,这又是一个新的飞起的轮子吗?这里重申一下,PWA背后不是一种新的技术,而是集合当前多种web技术的一种集合。分别利用各自的功能来完成渐进式的整体需求。下面就沿着前面提出的问题分别了解一下相关技术
由以下几种技术构成:
其中Service Worker是PWA技术的关键,它们可以让app满足上面的三基准。其他技术则是锦上添花,让app更加的强大。
离线缓存背景
针对网页的体验,从前到后都做了很多努力,极力去降低响应时间,这里就不表述多样的技术手段。
另一个方向的就是缓存,减少与服务器非必要的交互,不过对于离线的情况下浏览器缓存就无力了,
这样离线缓存的需求就出现了。
离线缓存的历程
web应用在离线缓存发展的过程中也不是一簇而就的,经历了逐渐完善的过程。
初期的解决方案是AppCache
然而,事实证明这是一个失败的尝试,缺陷太多,已经被废弃了。具体可以查看Application Cache is a douchebag
但是方向还是正确的,那就继续孜孜不倦的探索。
workers
持久化先放一边,来谈谈另一个问题
基于浏览器中的 javaScript 单线程的现实逐渐不能满足现代web需求的现状,例如耗时的计算,用户的交互显然会受影响。
为了将这些耗时操作从主线程中解放出来,早期W3C新增了一个Web Worker 的 API,可以脱离主线程单独执行,并且可以与主线程交互。
不过Web Worker是临时性的依赖于创建页面 ,不能满足我们持久化的需求。
冲着这个目标,下面就比较容易解决了,搞个能持久存在的就行了。
在Web Worker的基础上,W3C新增了service worker来满足我们持久化的需求。
其生命周期与页面无关,关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动
功能
Service Worker虽然满足了离线缓存来,其功能可不仅仅局限于此。 可以提供
管理资源缓存
这些正好也是PWA的目的,所以说Service Worker是PWA的关键技术。
前提条件
Service Worker 出于安全性和其实现原理,在使用的时候有一定的前提条件。依赖 Promise 实现
由上可知,不是所有的浏览器都支持的,支持情况大概如下:
iOS 内的所有的浏览器都基于 safari,所以iOS要在11.3以上
IE是放弃支持了,不过Edge好歹支持了。
Cache是Service Worker衍生出来的API,配合Service Worker实现对资源请求的缓存。
不过cache并不直接缓存字符串,而是直接缓存资源请求(css、js、html等)。
cache也是key-value形式,一般来说key就是request,value就是response
注册即声明sw文件的位置,显然应该在主js中引入。大概如下:
//基于promise
function registerServiceWorker(){
// 注册service worker
return navigator.serviceWorker.register('./sw1.js').then(registration => {
console.log('注册成功');
// 返回
return registration;
})
.catch(err => {
console.error('注册失败', err);
});
}
window.onload = function () {
//是否支持
if (!('serviceWorker' in navigator)) {
return;
}
registerServiceWorker()
}
Service worker 有一个独立于web 页面的生命周期。
如果在网站上安装 serice worker ,你需要注册,注册后浏览器会在后台安装 service worker。然后进入下面的不同阶段。
激活之后,service worker 将控制所有的页面,纳入它的范围,不过第一次在页面注册 service worker 时不会控制页面,直到它再次加载。
service worker 生效之后,它会处于下面两种状态之一:
由上图看知,分为这么几个阶段:
了解声明周期其实是为了我们在不同时间段去监听事件来完成相应操作。对PWA来说主要两个事件。
event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。
event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
const CURCACHE = 'CURCACHE_test_1'
const RUNTIME = 'runtime';
const CURCACHE_URLS = [
'./',
'/asset/sw.jpg',
'index.js'
]
self.addEventListener('install',e=>{
e.waitUntil(
//存储缓存路径对应的资源
caches.open(CURCACHE).then(cache=>{
cache.addAll(CURCACHE_URLS)
}).then(
self.skipWaiting()
)
)
})
//代理请求,使用缓存,请求发送之前
self.addEventListener('fetch', e => {
e.respondWith(
//缓存是否匹配
caches.match(e.request).then(function(response) {
if (response != null) {
//命中缓存返回缓存,结束请求
return response
}
//未命中缓存,正常请求
return fetch(e.request.url)
})
)
});
更新service worker
service worker 更新步骤如下:
const CURCACHE = 'precache_test_1'
//假设上个版本的key为precache_test_2 反正不等于CURCACHE
self.addEventListener('activate', e => {
e.waitUntil(
//遍历当前缓存keys
caches.keys().then(cacheNames=>{
return Promise.all(
cacheNames.map(function(cacheName) {
//是否等于当前key,保留自己
if (cacheName !== CURCACHE) {
return caches.delete(cacheName);
}
})
)}).then(() => self.clients.claim())
)
})
这样一个简单的service worker离线缓存完成了。控制台可以看到,来源是service worker
关闭网络之后再次访问,可以同样得到上面的结果,并且sw.js请求未能拿到,但是不影响,旧的文件依然在,这里证明了每次都回去对比sw文件以确保更新
到这里,离线缓存就实现了。
允许将站点添加至主屏幕,是 PWA 提供的一项重要功能。这样就不用再依赖于浏览器作为平台,符合移动端的用户习惯。
需要 manifest.json 文件去配置应用的图标、名称等基本信息如下:
{
//被提示安装应用时出现的文本
"name": "PQJ-PWA",
//添加至主屏幕后的文本
"short_name":"PQJ",
"description": "测试demo",
//添加之后,启动地址
"start_url": "/index.html",
//图标信息
"icons": {
"128": "/asset/sw.jpg"
},
"developer": {
"name": "pqj",
"url": ""
},
"display": "standalone",
"background_color": "#287fc5",
"theme_color": "#fff",
"permissions": {
"desktop-notification": {
"description": "Needed for creating system notifications."
}
}
}
然后以如下方式在html中引入
这样完成之后,移动端安卓使用chrome(亲测),首次访问时会提示是否允许安装到主屏幕,以应用icon的形式出现。
图片和文字即由配置决定。
消息通知也是使用service worker的通知功能进行的,允许服务器想用户发生通知,而非用户主动请求才去响应某些行为。
正常的通知逻辑需要服务器来参与实现,这次展示只实现功能。
function getPermission(){
return new Promise((resolve, reject) => {
//权限获取
const permissionPromise = Notification.requestPermission(result => {
resolve(result);
});
}).then(result => {
//判断条件
if (result === 'granted') {
execute();
}
else {
console.log('no permission');
}
});
}
发送通知
function execute() {
// 允许之后执行
registerServiceWorker().then(registration => {
// 通知
registration.showNotification('Hello World!');
});
}
参考文档
https://lavas.baidu.com/doc
https://developer.mozilla.org/zh-CN/Apps/Progressive
至此,本文介绍就结束了,更多请参考实例虽然PWA目前来看,面对的限制还很多,但是也可以看出web组织在更好的提升web应用方向上做的努力。正如一直提到的那句话,未来可期。
目前国内百度这方面做的比较成熟,新浪微博已经有了pwa 测试版。