PWA 全称是渐进式 web 应用,它是用一系列前端技术来实现的,目标是提供类似原生APP一样的体验。主要解决的痛点是:
- 网页离线无法访问
- 缓存不可编程,即不能通过 js 控制缓存的增删改查,只能通过修改资源的hash之类的方式让缓存失效
- 没有桌面入口,只能通过浏览器打开
- 无消息推送功能
以下介绍 PWA 相关技术
1、Service Worker
PWA中最重要的一项技术,特点是:
- 单独起线程,不影响主JS线程
- 是个浏览器后台线程,由于脱离了浏览器窗体,故无法访问DOM和window
- 设计为全异步,故不能使用 XHR 和 localStorage 等
- 必须使用https协议(开发环境除外)
- 桌面端Chrome、Firefox、Safari可用,IE不可用,Edge可用;移动端safari>=11.4可用;android>=67可用
- sw.js 有路径的概念,所以一般放在项目根目录,方便管理所有的页面;‘/path/sw.js’只能管理path路径下的页面
navigator.serviceWorker.register('/sw.js', { scope: '/js' }) // 第二个参数制定 sw.js 管理的范围
- 可使用 postMessage实现sw.js 和主 JS 进程双向通信
1、sw.js => 主js,借用了 clients 全局对象,每个client 代表一个 tab 窗口
// sw.js
const allClients = await clients.matchAll();
allClients.forEach(client => client.postMessage(msg));
// 主 js
if("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", function(event) {
let msg = event.data;
console.log('message',msg)
});
}
2、主 js => sw.js
// 主 js
navigator.serviceWorker.controller.postMessage({
type: 1,
desc: "remove html cache",
url: window.location.href}
);
// sw.js
this.addEventListener("message", function(event) {
let msg = event.data;
console.log(msg);
});
-
缓存空间很大,不用担心不够用
-
事件
install 安装后,activate 激活后,fetch 代理请求。sync 恢复网络时做些事情
install 用来缓存文件,activate 用来缓存更新,fetch用来拦截请求直接返回缓存数据。三者齐心,构成了完成的缓存控制结构。sync 事件可以知道什么时候恢复了网络 -
一个demo
// 主 js 注册 serviceWorker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw-demo-cache.js');
}
// sw-demo-cache.js
var VERSION = 'v1';
// 开始缓存
self.addEventListener('install', function(event) {
this.skipWaiting(); // 避免更新后的 service-worker 处于等待状态
event.waitUntil(
caches.open(VERSION).then(function(cache) {
return cache.addAll([
'./start.html',
'./static/jquery.min.js',
'./static/mm1.jpg'
]);
})
);
});
// 更新缓存
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
// 如果当前版本和缓存版本不一致
if (cacheName !== VERSION) {
return caches.delete(cacheName);
}
})
);
})
);
});
// 捕获请求并返回缓存数据
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (resp) {
if (resp) {
console.log(new Date(), 'fetch ', event.request.url, '有缓存,从缓存中取')
return resp
} else {
console.log(new Date(), 'fetch ', event.request.url, '没有缓存,网络获取')
return fetch(event.request)
.then(function (response) {
return caches.open(VERSION).then(function (cache) {
cache.put(event.request, response.clone())
return response
})
})
}
})
)
})
- 缺点
一个资源需要更新,就得放弃所有其他不需要更新的资源缓存。所以一般要配合 http 缓存一起使用。当 service-worker 缓存失效时,再走 http 缓存
2、Cache和CacheStorage
caches 缓存库一般以request 为key , response为value
- 创建 caches 缓存库
caches.open(CACHE_NAME);
- 添加缓存 put(key,value) or addAll(resourseArray)
// 请求资源并添加到缓存里面去
caches.open(CACHE_NAME).then(cache => {
cache.addAll(cacheResources);
// cache.put(request, resource);
})
- 查看缓存是否存在 match(key)
caches.match(event.request).then(response => {
// cache hit
if (response) {
return response;
}
})
- 删除缓存 delete(key)
caches.open(CACHE_NAME).then(cache => {
console.log("delete cache " + url);
cache.delete(url, {ignoreVary: true});
});
3、Web App Manifest添加桌面入口
在项目根目录下准备一个 manifest.json 文件,可以实现用户首次访问网页时提示在桌面创建网页入口icon,以后直接通过桌面的icon就可以直接访问网页了。
注意:只有至少已经访问网站两次、访问至少间隔五分钟时才可以将网络应用添加到主屏幕上。
"short_name": "人人FED", // 桌面应用的名字
"name": "人人网FED,专注于前端技术", // 启动时欢迎语
"icons": [ // 启动图标
{
"src": "/html/app-manifest/logo_48.png",
"type": "image/png",
"sizes": "48x48"
},
{
"src": "/html/app-manifest/logo_96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "/html/app-manifest/logo_192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/html/app-manifest/logo_512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/?launcher=true", // 这个地址要被 service worker 缓存起来
"display": "standalone", // 启动后隐藏浏览器地址栏
"background_color": "#287fc5",
"theme_color": "#fff"
}
然后在 html 文件里引入该文件
<link rel="manifest" href="/html/app-manifest/manifest.json">
4、消息提醒与信息推送
- 消息提醒
主 js
// 1、ask for permission
Notification.requestPermission(permission => {
console.log('permission:', permission);
});
// 2、display notification
displayNotification(msg)
function displayNotification(msg) {
if (Notification.permission == 'granted') {
navigator.serviceWorker.getRegistration()
.then(registration => {
registration.showNotification(msg);
});
}
}
sw.js
self.addEventListener('notificationclick', event => {
// 消息提醒被点击的事件
event.waitUntil(clients.openWindow('https://baidu.com'))
});
self.addEventListener('notificationclose', event => {
// 消息提醒被关闭的事件
});
-
消息推送
需要后端配合,比较麻烦,暂时放弃
消息推送流程
网友评论