前言
之前看到有使用ant-design-pro的网站,在服务端更新后,前端会提示刷新页面。但是在调研后发现service-worker.js的更新,只会在重刷页面时检测到,并不会在已经开的tab里被检测到。所以之前看到的可能的情况可能是:有两个tab,一个tab刷新,另外一个tab就能检测service-worker.js。这样的话也不好用。
概述
PWA(Progressive Web App)渐进式网页应用,所谓渐进式,意思就是主张很少,你不用遵守一系列的约束条件。提升 Web App 体验,能给用户原生应用的体验
特性
- 跨平台,可安装,独立窗口,沉浸式体验
- 消息推送
- 缓存
- 离线可用
实践
从四个特性来详细讲讲实践效果。
app安装
条件
- 在header头里,提供manifest
<link rel="manifest" href="/assets/manifest.json">
-
只https可用
-
manifest.json里提供icon,size > 144
-
注册service worker
效果
略
小结
可以像app一样,安装在桌面,并提供沉浸式的体验,但是感觉和safari浏览器打开有点一样,与safari的区别就是最顶上的mac工具栏没有了。
消息推送
在service-worker.js里监听push事件,并使用self.registration.showNotification(title, options)。需要依赖Notification api。
使用
服务端使用web-push
- 在service-worker.js里activate时,向node发送注册subscribe,node把subscription对象存下来.
- 在service-worker.js里监听push事件
- node调用
webpush.sendNotification(subscription, dataToSend)
效果
略,就是桌面上安装个app一样,
代码示例
- 初始化
serivce-worker.js
self.addEventListener('activate', () => {
self.registration.pushManager.subscribe().then((subscription) => {
fetch(SERVER_URL, {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription)
})
})
})
node
// 初始化subscription
app.post('/save-subscription', async (req, res) => {
const subscription = req.body;
await saveToDatabase(subscription);
res.json({ message: 'success' });
});
// 发送消息
app.get('/send-notification', (req, res) => {
const subscription = db.subscription;
const message = 'Hello World';
webpush.sendNotification(subscription, message);
res.json({ message: 'message sent' });
})
- Push listener
self.addEventListener('push', function(event) {
const title = 'Push service';
const options = {
body: 'Yay it works.',
icon: 'images/icon.png',
badge: 'images/badge.png'
};
event.waitUntil(self.registration.showNotification(title, options));
});
- Notification api
document.addEventListener('DOMContentLoaded', function () {
if (!Notification) {
console.log('Notification not support')
return;
}
if (Notification.permission !== 'granted') {
Notification.requestPermission();
}
});
function notifyMe() {
if (Notification.permission !== 'granted') {
Notification.requestPermission();
} else {
const notification = new Notification('Notification opened', {
icon: 'xxx.png',
body: 'hello',
});
notification.onclick = function () {
window.open('xxx');
};
}
}
小结
- 单实例做broadcast,可以将缓存里的subscription遍历,并发消息。但是分布式集群很难做,不过也需要像websocket一样,再依赖redis、kafka
缓存
利用service worker来做静态资源的缓存,根据匹配url、正则,第一次获取将资源存在Cache Storage里,如果service-worker.js有更新,会重新获取最新的资源,并再次cache。支持离线缓存
tips:跨域缓存需要 workbox-cacheable-response 插件
缓存策略
- CacheFirst:缓存、回退到网络。适用于离线优先方式构建,对缓存中的资源提供缓存,对未在缓存中的资源提供网络请求并缓存下来
- CacheOnly:仅缓存,适用于静态资源
- NetworkFirst: 网络、回退到缓存。为在线用户提供最新内容,但离线用户会获得较旧的缓存版本。适用于频繁更新的资源
- NetworkOnly:仅网络请求,适用于没有相应离线资源的对象
- StaleWhileRevalidate:缓存、网络同时进行,适用于网络比硬盘读取更快。会造成流量浪费
缓存优先级
- Service Worker,可自行操作缓存策略
- Memory Cache
- Disk Cache(http缓存),server下发http头控制。响应内容的引用存入Memory Cahce
- 网络请求
效果
代码示例
workbox.routing.registerRoute(
/.*\.(?:|png|gif|jpg|jpeg|svg|mp4|js|css|image)$/,
new workbox.strategies.CacheFirst({
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
小结
- service worker缓存应用场景:离线缓存。但感觉静态资源有cdn的支持,走memory cache、disk cache足够了。
- HTTP缓存空间有限,容易被冲掉。多一层缓存,命中的概率更高了。
离线可用
监听fetch事件,如果是路由请求,则返回离线html
self.addEventListener('fetch', (evt) => {
if (evt.request.mode !== 'navigate') {
return;
}
evt.respondWith(
fetch(evt.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match('offline.html');
});
})
);
});
小结
可以用,但没啥必要
整体总结
- web app安装可取,可提升体验。其它不是很需要。