PWA渐进式Web应用
文末附上全部源码。PWA是什么?Progressive Web App,即 渐进式网络应用。可以循序渐进的实现它,每次实现一部分业务。降低了业务的部署成本、适应了Browser厂商的更新节奏。PWA组成技术:Service WorkerPromisefetchcache APINotification API1. Service Worker:(PWA最重...
文末附上全部源码。
PWA是什么?
Progressive Web App,即 渐进式网络应用。
可以循序渐进的实现它,每次实现一部分业务。降低了业务的部署成本、适应了Browser厂商的更新节奏。
PWA组成技术:
- Service Worker
- Promise
- fetch
- cache API
- Notification API
1. Service Worker:(PWA最重要的API)
服务工作线程。
Web Worker是一种独立于浏览器主线程的环境,用来执行比较复杂的计算操作,不会阻塞页面的渲染,与主线程通过postMessage通信。
而这里的Service Worker和Web Worker类似并拥有更多特性:
- 独立页面,常驻内存运行。
- 代理请求。
- 只在HTTPS下的环境下运行。(依赖HTTPS)
2.Promise:
“承诺”控制流。
new Promise((resolve,reject)=>{})
.then(...)
.catch(...)
Promise的特点:
- 优化回调地狱
- 配合async/await语法同步化
- service Worker的api风格
3.fetch:
现在很多浏览器都支持了。
fetch特点:
- 比传统的XMLHttpRequest更简洁
- Promise风格
4.cache API:
支持资源的缓存系统。
其特点:
- 缓存资源.例如css script image等
- 依赖Service Worker代理网络
- 支持离线程序运行
5.Notification API:
消息推送。
Web界面和APP都是可以发送通知的。
特点:
- 依赖用户授权
- 依赖常驻内存的线程,适合在ServiceWorker中推送
具体使用:
第一部分: ServiceWorker:
1.注册ServiceWorker:
先启动一个服务器:
yarn add serve global
然后serve启动,打开浏览器:
说明ServiceWorker注册成功了!
还有一个地方可以观察和调试ServiceWorker:
可以看到右边显示了serviceWorker的当前状态.
2.开始编写ServiceWorker:
需要注意的事情是:
- 不能在上下文中访问DOM
- 不能访问诸如Window、LocalStorage一类的对象
- 只有一些特定的对象可以访问:如self代表serviceWorker的全局作用域对象.只能在self上添加监听事件,才能与serviceWorker打交道。
- 其生命周期:install、activate、fetch
ServiceWorker编程就是与ServiceWorker的生命周期打交道。
install事件是在一个新的ServiceWorker脚本被安装后触发,只要内容有一点不同,浏览器就会认为是新的版本。新的版本会被下载、安装,但不会立即生效。因为当前生效的是上一个版本。
现在修改下源文件:
可以看到浏览器因为serviceWorker的一点改变而认为是新的版本,对新版本进行下载安装但并未激活。
最新版本的代码处于waiting状态:
点击skipwaiting强制启用activate新版本:
1.fetch事件的关键操作:
fetch事件是专门用于捕获资源请求的:
先新创建一个外链资源
然后html中引入这个css外链文件:
这就是一个典型的外链资源。
此时刷新浏览器,可以看到:
index.css的外链资源的请求被捕获到了。
fetch事件可配合cache API可实现本地代理功能。
2.install事件的2个关键操作:
回看install事件:
再回到浏览器,清除所有的servieWorker:
此时刷新浏览器可以看到install事件触发,然后等待5秒后activate事件被触发:
这就是waitUntil()的功能,其会配合特定的行为,比如上面application选项卡中点击的skipWaiting():
强制停止了旧的serviceWorker,激活新的serviceWorker.现在只要serviceWorker的脚本有更新,刷新浏览器后都会直接安装激活新版本ServiceWorker.
3.activate事件的2个关键操作:
2.1 和install事件中的一样:
2.2 self.clients.claim()
这里的clients指的是serviceWorker控制的所有页面。
这个方法能够页面在首次加载后同样受到serviceWorker的控制,在默认情况下,首次是不受控制的。
4. 除了3种生命周期事件之外的事件:
推送事件和同步事件.
第二部分: Promise:
它主要是处理异步逻辑的。
这里,假设一个业务是异步读取xml文件,传统的方式callback:
当嵌套多层时就会产生典型的回调地狱。
用promise:
上面的2种写法不等价。
对于第1种写法,如果xml=>{}函数抛出异常,后面的err=>{}是不能捕获到的。对于第2种写法catch就能捕获到所有异常。
上面的promise支持类对象的行为。
在promise的构造方法上还有几个有用的静态方法:
这是一个快捷方式,可以把入参转换为一个完成的promise.
可以把入参转换为一个拒绝的promise.
2者的等价写法为:
其他的静态方法:
一般传入由promise实例对象组成的数组,并返回一个新的Promise。只有当所有的promise都完成后,返回的promise才完成。只要其中一个promise被拒绝,那么返回的promise会被立刻触发拒绝。Promise.all()在执行一系列并行的任务时有用。如果入参数组种的某个成员不是promise,那么可以用promise.resolve()包装一下。
还有一个是Promise.race()。其是只要有其中一项完成或拒绝,那么返回的promise就立刻完成或拒绝。
其实Promise还是有弊端,所以出现了async/await:
第三部分: 网络请求Fetch:
fetch()函数是一个全局函数,用来发起http请求。
先看看如何用传统的XHR发起request请求:
而用fetch():
fetch的更多选项:
还可以将第2个参数封装为独立的Request对象:
注意:serviceWorker中无法访问XMLHttpRequest,fetch是我们唯一的选择.
fetch是比较低级API,不能提供上传进度,也不能控制超时时间,更不能主动中断请求,所以页面中使用axios模块。
第四部分: 资源的缓存系统Cache API:(PWA的顶梁柱)
让web应用在离线环境下运行成为可能。
先说serviceWorker的3个核心生命周期:install、activate、fetch
- install事件发生在新的serviceWorker下载之后。(发生1次且只发生1次)
- activate事件发生在新的 serviceWorker被启用之时。(发生1次且只发生1次)
- fetch事件发生在捕获到资源请求之时。(发生无数次)
这里以catchAPI来做做资源代理,实现页面的离线可用功能:
为了实现需求,在上面的3个生命周期方法中都要相应修改:
- install中应拉取并缓存必要的资源。
- activate中应清除旧版本遗留下来的不用的缓存。
- fetch中应在捕获到资源请求后去查询并返回缓存中的资源。
一般在页面上线之前,都会得到一个可能用到的静态资源的集合。有必要将集合中包含的资源全部写入到缓存中。
这个集合的内容一般可以在编译构建期间得到,只需要操作cache把他们写入缓存即可。
代码解释:
先打开特定的缓存空间,然后写入必要的资源数据。这能确保serviceWorker激活之后立刻就能响应特定的资源请求。
serviceWorker上下文中可以设置多个缓存空间,所有缓存空间的集合是caches,caches是一个全局对象,可以直接使用。
其open()打开一个缓存空间,open()需要传入一个缓存名字。open()方法得到的是一个promise,在其then()中可以得到缓存空间的句柄cache。用句柄cache就可以直接写入缓存了。addAll()需要传入一个数组,数组的每一项是资源的路径,例如把当前的html也买你和css写入到缓存中。【这里的资源列表应该在i项目构建期间自动得到,不能人工维护】
然后是使用缓存:
在fetch()中可以捕获到html和css等所有的资源请求,然后去cache中查询,如果查到了就返回缓存,否则就发起网络请求进行获取。
第二次刷新浏览器后,查看调试:
说明缓存写入到了浏览器中。
这个时候关闭http服务器:
再刷新页面可以看到即使页面离线,只要有足够的缓存资源,界面Web依然能够运行。
但是还没完,缓存是可以变化的,每次缓存变化最好修改cache的名字重新抓取资源写入新的缓存,还应清理以前的无用缓存。
activate事件就是清理旧缓存的最佳时间:
还是在waitUntil里面写,因为我们希望在激活之前完成cache的清理。
现在手动更改一下cache的版本号:
再启动http服务器:
可以看到新的serviceWorker已经开始安装了
当前占用的缓存为6.5kb,因为当前占用的缓存有2份。
然后点击skipWaiting,立即激活新版本serviceWorker:
发现缓存变少了。即清理了旧缓存。
cache API不仅仅可以在serviceWorker中使用。
也可以在页面上下文中调用。
e.g: onload后写缓存
第五部分: Notification API
通知。在app中是为了拉回客户到app中。也有很多其他功能。
web 应用期望即使页面关闭了,只要浏览器进程还在就有弹出通知的途径。
ServiceWorker就是最适合弹出通知的地方,ServiceWorker还可以接收到push事件。
Notifiaction在页面上下文、ServiceWorker上下文都能用,但有差别。
1.页面上下文中使用Notification API:
可以直接获取到Notification全局对象,它也是一个构造函数。
作为对象,它有一个permission属性,代表当前页面已经获取到的授权,并通知需要打扰用户的,受到严格的授权限制。
permission属性有3个值:
- default:代表用户没有同意也没有拒绝。这时应该向用户弹出授权请求。除非已经明确授权过,否则通知弹不出来。
- denied:禁止/否定
- granted:允许。
弹出授权:
点击禁止按钮:
再弹出一次授权请求:
Notification.requestPermission().then(permission=>console.log(permission))
点击允许按钮:
这样就授权通过了,可以弹通知了,这个时候Notification就变成了一个构造函数,创建一个Notification实例就相当于弹出了一个通知:
2.ServiceWorker中使用Notification API:
在ServiceWorker中Notification.permission默认是denied,这是因为ServiceWorker不允许弹出授权请求。因为反正default也不能弹出通知,那么就当作denied处理了。
要i想获取授权就必须在页面的上下文中请求。
先切换到页面上下文中:
点击允许,再回到ServiceWorker上下文环境:
已经被允许了。
还需要注意的是:创建通知也和在页面中创建通知不一样:
- 不允许把Notification当作构造函数来创建通知
- 只有一种方法:
这个registration就是我们在页面上下文中注册ServiceWorker成功后得到的对象:
只不过在页面上下文中没有showNotification()方法。
第六部分: 在项目中开启PWA
Google的Workbox:一个library,利用PWA技术,赋予应用能离线运行的能力。
React中,Workbox在webpack中有对应的插件。
create-react-app已经配置好了workbox.在package.json中可以找到workbox-webpack-plugin:xxxx
React模板项目下的src下有serviceWorker.js文件。并查看index.js下模板的注释,使用方法在注释中已经写明了。
全部源码:
1.demo项目结构:
2.index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>PWA</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>PWA 渐进式web应用</h1>
<script>
//通常useragent就挂载在navigator下
//serviceWorker是单例对象,不可以用构造函数创建新的实例。
//register方法的第1个参数是serviceWorker外链脚本地址,第2个参数是一个选项对象。
//scope对象代表的是可以控制的页面的相对路径,默认是脚本本身所在的路径。【这里传入根路径代表控制所有的页面】
navigator.serviceWorker.register('./serviceWorker.js',{scope:'/'}).then(ServiceWorkerRegistration => {
console.log(ServiceWorkerRegistration);
},err => {
console.error(err);
})
</script>
</body>
</html>
3.serviceWorker.js:
// const CACHE_NAME ='cache-v1';
const CACHE_NAME ='cache-v2';
self.addEventListener('install',event => {
console.log('install',event);
event.waitUntil(caches.open(CACHE_NAME).then(cache=>{
cache.addAll([
'/',
'./index.css'
]);
}));
});
self.addEventListener('activate',event => {
console.log('activate',event);
event.waitUntil(caches.keys().then(cacheNames=>{
//清除我们自己不需要的这里
return Promise.all(cacheNames.map(cachename =>{
if(cachename!== CACHE_NAME){
return caches.delete(cachename);//注意需要return ,因为delete也是需要返回一个promise
}
}));
}));
});
self.addEventListener('fetch',event => {
console.log('fetch',event);
event.respondWith(caches.open(CACHE_NAME).then(cache => {
//判断里面有没有当前请求的资源
return cache.match(event.request).then(response =>{
if(response){//如果存在就直接返回该资源缓存
return response;
}
//不存在,只有发起网络请求然后并加入缓存,避免下次还网络请求该资源
return fetch(event.request).then(response=>{
//这里response是流式的,只能读取一次。为了缓存可读取,需要克隆一份出来
cache.put(event.request,response.clone());
return response;
});
});
}));
});
Google关于pwa的介绍:
https://codelabs.developers.google.com/codelabs/your-first-pwapp/#0
更多推荐
所有评论(0)