PWA
PWA(Progressive Web App)是一种结合了Web应用和原生应用优点的技术,如Service Worker和Web App Manifest,来实现离线缓存、推送通知等功能,从而提升用户体验
PWA核心特征
- 快速响应:PWA可以在离线或网络状态不佳时,通过Service Workers和Cache API提供流畅的页面访问体验
- 离线工作:通过Service Workers缓存关键资源使得用户在无网络情况下访问网站
- 跨平台支持:PWA可以在Windows、Android、iOS、macOS等平台的浏览器上运行
Service Worker
Service Worker 是PWA的关键技术之一,它是运行在Web应用后台的独立线程,可以拦截和控制网络请求,充当Web应用与浏览器网络的代理服务器,主要应用于提供离线支持、缓存资源等功能
Service Worker生命周期
- 安装(install):首次注册Service Worker后浏览器会尝试下载并安装它
- 激活(active):安装完成后,Service Worker进入激活阶段,在此期间它可以控制页面,并清理旧版本的资源
- 控制(control):激活之后,Service Worker可以控制页面,拦截网络请求并提供缓存策略
- 更新(update):每当Service Worker文件被修改,浏览器会在下次页面加载时尝试更新它
- 冗余(redundant):若一个版本的Service Worker长时间没有活动,会被标记为冗余并可能被回收
Workerbox
Workerbox是基于JavaScript的库,依赖于Service Worker技术,为开发者提供了灵活的缓存策略
Workerbox提供的主要功能:
- clientsClaim:确保Service Worker在安装时立即控制所有页面
- skipWaiting:运行Service Worker在安装后立即激活
- Runtime-Caching:配置缓存策略,可根据url模式指定不同的缓存策略
常见的缓存策略:
- Cache-First:优先从缓存中获取资源,如果缓存中没有,则获取网络资源
- NetWork-First:优先获取网络资源,如果失败,则从缓存中获取
- Cache-Only:只从缓存中获取资源
- NetWork-Only:只从网络中获取资源
- Stale-While-Revalidate:在缓存的资源仍然有效时返回缓存资源,同时从网络中获取最新的资源更新缓存
常见问题
- Service Worker只能在HTTPS环境下注册成功(或在localhost上进行开发运行)
- service-worker.js的更新可能在静态资源加载之后,导致页面静态资源更新滞后,需要连续两次进入页面方可更新到最新版本
- 缓存内容不宜过大,否则影响使用性能
- 对浏览器版本有要求
浏览器支持度
(详见https://caniuse.com/?search=Service%20Worker)
image.png
webpack接入Service Worker
安装workbox-webpack-plugin
npm i workbox-webpack-plugin --save-dev
修改webpack.config.js
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
module.exports = {
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /\.html$/,
handler: 'NetWorkFirst',
options:{
cacheName: 'htmlCache',
expiration: {
maxAgeSeconds: 24 * 60 * 6, //1天
}
}
},
{
urlPattern: /\.(?:js|jsx|png|jpg|jpeg|gif|svg|css)$/,
handler: 'CacheFirst',
options:{
cacheName: 'staticCache',
expiration: {
maxAgeSeconds: 24 * 60 * 6 * 30, //30天
}
}
}
]
})
]
}
注册Service worker
if(navigator.serviceWorker){
window.addEventListener('load', function(){
navigator.serviceWorker.register('./service-worker.js', {scope: './'})
.then(function(registration){
console.log('ServiceWorker registration success' + registration.scope)
})
.catch(function(err){
console.log('ServiceWorker registration failed' + err)
})
})
}else{
console.log('servicrWorker is undefined')
}