Fork me on GitHub

PWA核心模块介绍

PWA 简介

Progressive Web App 渐进式web应用程序

PWA是由一系列特性组成。

Service Worker

整个 pwa 系统的大脑。类似于Web Worker但是具备有更多功能,他是pwa里面最重要的功能。
服务工作线程

  • 常驻内存运行
  • 代理网络请求
  • 依赖 https

Promise

“承诺”控制流

  • 优化回调地狱
  • async/await语法同步化
  • service worker的很多apipromise化了
1
2
new Promise((resolve,reject)=>{})
.then().then().catch(...)

fetch

网络请求

1
fetch('/xxx/xxx').then(res=>res.json()).then(info=>...)
  • XMLHttpRequest更简洁
  • Promise风格
  • 存在一些不足

cache API

支持资源的缓存系统

  • 缓存资源(css/scripts/image)
  • 依赖Service Worker代理网络请求
  • 支持离线程序运行(PWA的一个很重要的特性)

Notification API

消息推送

  • 支持电脑的消息推送

Service-Worker

对于浏览器有要求的,而且这个是不能模拟的.

我们可以新建一个pwa-case的目录来写这些demo,使用serve来启动一个http服务器.

1
npm install serve -g

在里面touch一个index.htmlsw.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!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" />
<link rel="stylesheet" href="./index.css" />
<title>PWA CASE</title>
</head>
<body>
<script>
// scope是当前脚本控制的页面
navigator.serviceWorker.register("./sw.js", { scope: "./" }).then(
(res) => {
// 成功注册了service-worker,可以在控制台看见,也可以在Application的Service Workers里面查看
console.log(res);
},
(err) => {
console.log(err);
},
);
</script>
</body>
</html>

我们在html页面里面注册一个service-worker的脚本,然后开始使用起来。

service-worker里面最关键的api还是它的几个生命周期函数(这里就包含了三个生命周期函数,install,activate,fetch):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在一个新的service-worker安装就就会被触发
self.addEventListener("install", (event) => {
console.log("install: ", event);
// promise完成后,install 才相当于真正的完成了,它会推迟activate的时间,这里会等待5s
// event.waitUntil(new Promise(resolve=>{
// setTimeout(resolve,5000);
// }))
// self.skipWating强制停止旧的service-workder,开启新的,强行挤掉旧版本的
// event.waitUntil(self.skipWaiting());
});
// 激活,代表service-worker事件被正式启动
self.addEventListener("activate", (event) => {
console.log("activate: ", event);
event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", (event) => {
console.log("fetch: ", event);
});

在这个js代码里面fecth使用监听文件的网络请求的,只要html文件去获取新的资源(比如在里面去调用一个css文件),都会被监听出来。然后install主要是在service-workder安装的时候触发的,而activate则是在service-worker在被激活的时候触发。

里面有一些重要的api例如event.waitUntil()self.skipWaiting())都是十分重要的。

Promise

这个api不做过多讲解,写多了都知道的。

fetch

1
2
3
4
5
6
7
8
9
10
11
12
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.onreadystatechange = () => {
// 等于4代表请求结束了
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.sattus < 300) {
console.log(xhr.response);
}
}
};
xhr.open("GET", "/userinfo.json", true);
xhr.send(null);

如果使用fetch去处理这种请求的话,可以直接一行代码就解决问题:

1
2
3
4
5
fetch("/useinfo.json")
.then((res) => res.json())
.then((info) => {
console.log(info);
});

当然fetch的第二个参数可以填写很多其他的东西,例如一些请求头,请求类型之类的参数。

cache API

除了service worker之外,那么cache API就是pwa的第二个顶梁柱了。

现在我们准备使用cache API来实现一个离线缓存的功能。

还是用之前在service-worker那边使用过的demo来作为例子:

htmlbody标签里面加上一个html元素用于标识。

1
<h1>Hello PWA</h1>

然后在sw.js里面使用之前的sw生命周期函数去进行一个cache的操作。
大致流程是这样的:

  1. service-worker被安装的时候写入缓存资源(同时要选择好缓存的文件,这里选择的就是html自身和它的css文件)
  2. 然后在进行资源的请求的时候(fetch这个生命周期函数这一步的时候),先去缓存里面查找,如果缓存命中了的话,那么就走缓存,否则就利用fetch去请求资源,然后再使用缓存去存一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const CHACHE_NAME = 'cache-v1';
// 在一个新的service-worker安装就就会被触发
self.addEventListener('install',event=>{
console.log('install: ', event);
// 打开一个缓存空间
event.waitUntil(caches.open(CHACHE_NAME).then(cache => {
// 写入缓存的资源
cache.addAll([
'/',
'./index.css'
])
}));
});
// 激活,代表service-worker事件被正式启动
self.addEventListener('activate',event=>{
console.log('activate: ', event);
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch',event=>{
console.log('fetch: ', event);
// 去cache里面找资源,找到就返回,找不到就利用网络请求去拿
event.respondWith(caches.open(CHACHE_NAME).then(cache => {
return cache.match(event.request).then(res => {
// 如果res存在,就表示缓存命中了
if(res){
return res;
}
return fetch(event.request).then(res=>{
cache.push(event.request,res.clone());
return res;
})
})
}))
});

然后我们用serve启动项目之后,打开localhost:5000第一次看network里面的资源还是来自网络请求,可是当我们第二次刷新的时候就会发现资源来自缓存了,然后我们关掉我们的应用服务,再去访问页面,会发现页面仍然能够访问。

这种方式就使得web app用起来和原生的app有一种差不多的感觉。

如果我们想要升级一下缓存的版本,我们需要在activate这个生命周期函数里面去进行一波处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 激活,代表service-worker事件被正式启动
self.addEventListener("activate", (event) => {
console.log("activate: ", event);
event.waitUntil(
caches.keys().then((cacheNames) => {
// 这里直接使用并行处理
return Promise.all(
cacheNames.map((item) => {
if (item !== CHACHE_NAME) {
return caches.delete(item);
}
}),
);
}),
);
});

然后修改CACHE_NAMEv2之后再刷新页面,会发现缓存的ApplicationClear Storage里面的内存由4.7kb上升到6.3kb,这是因为这个时候,这里会存在着两份缓存。我们要强制升级service-worker的版本的话,需要在service workers那里的黄色等待按钮那里点击skipWaiting按钮去进行一个强制的升级即可。

这个时候就会使用新的缓存策略。

Notification API

消息推送API

这个api我们都不用写代码,直接去console的控制台去使用就可以了。

1
2
3
4
Notification.permission()
// "default"
Notification.requestPermission().then(item => console.log(item))
// 把这行代码放在控制台上运行就会弹出浏览器的提示框了,然后会点击允许之后就会输出"granted",点击禁止之后输出"denied"

然后我们想要重置的话,直接去点击url上面的那个感叹号去设置成"询问(默认)"就可以了。

授权通过之后我们就可以弹出通知了.

第一个参数为标题,后面参数body里面的为内容。

1
new Notification("Hello PWA",{ body:"This is from PWA" })

这样在控制台运行之后,就会输出相对应的通知内容了。

如果我们重新将通知模式设置为默认,然后再在sw.js的环境下去输出Notification.permission()会发现默认输出就是denied…然后调用Notification.requestPermission()都会发现方法并不存在,因为在service worker下是不允许弹出授权请求的,因为有可能页面都打不开。。这样用户都不好直接判断是哪个页面在弹出通知。

所以我们需要切换成页面的上下文来进行一个消息的弹出。

注意切换上下文就是在console控制台那里切换JavaScript Context即可。当我们切换成页面的之后(即为top),然后使用Notification.requestPermission()允许页面弹出通知之后,再切换成sw.js的运行环境即可。

这里创建通知又需要使用另外一个API了,即为:

1
self.registration.showNotification('Hello PWA',{body:'My first PWA'})

这是因为这个API是在service-worker注册的时候产生出来的,它只在sw的运行环境下面才会存在。这样这个时候就可以弹出相对应的通知了。

在业务中开启pwa

create-react-app使用的是一个workbox-webpack-plugineject出来之后是能看到这个配置的,它不会在开发环境打开,但是生产环境会给我们打开出几个pwa离线缓存的文件。

-------------本文结束感谢您的阅读-------------