PWA
Service worker
是PWA
得以实现的核心技术
service worker
Service worker
是一个独立的worker
线程,独立于当前网页进程,是一种特殊的web worker
。主要功能在生命周期函数中实现。
service worker注册
if('serviceWorker' in navigator) {
const sw = await navigator.serviceWorker.register(serviceWorker文件路径);
}
//使用serviceworker-webpack-plugin插件注册方式
import runtime from 'serviceworker-webpack-plugin/lib/runtime'
if('serviceWorker' in navigator) {
const sw = await runtime.register();
}
serviceworker-webpack-plugin
插件可以将所有打包后的目录文件注入到打包后的sw.js文件,通过global.serviceWorkerOptions.assets
获取所有目录文件名,便于做静态资源的缓存。
PWA用到的service worker生命周期函数
-
install缓存所有你需要的静态资源
self.addEventListener('install', async () => { console.log('service worker installing'); const cache = await caches.open(CACHE_NAME); //cacheStorage中缓存的名称 const CACHE_URL = global.serviceWorkerOptions.assets.concat(['/']); //缓存的静态资源目录,不要忘记缓存'/'目录文件,在断网情况下,页面首先加载的是'/'目录资源。 await cache.addAll(CACHE_URL); //此处只要有一个资源无法下载,静态资源的缓存都会失败 await self.skipWaiting(); //跳过等待,保持运行最新的service worker })
-
active删除旧的缓存
self.addEventListener('activate', async () => { console.log('service worker activate'); const cacheKeys = await caches.keys(); cacheKeys.map(async item => { //删除旧的缓存 if (item !== CACHE_NAME) { await caches.delete(item); } }) await self.clients.claim();//接管所有页面 }
-
fetch可以拦截所有的请求,并做数据缓存
self.addEventListener('fetch', async e => { console.log('service worker fetch'); const req = e.request; const url = new URL(req.url); const api = new URL(apiHost); //自己使用的请求域名 let isNetworkerFirst, isCacheFirst; if (isNetworkFirst) { e.respondWith(networkFirst(req)); //网络优先 } else if (isCahceFirst) { e.respondWith(cacheFirst(req));//缓存优先 } }) const cacheFirst = async req => { //先从缓存中获取数据,如果没有匹配到,再发起网络请求 const cache = await caches.open(CACHE_NAME); let cacheData = await cache.match(req); if (!cacheData) { cacheData = await fetch(req); if (!cacheData || cacheData.status !== 200) return cacheData; const cache = await caches.open(CACHE_NAME); cache.put(req, cacheData.clone()); } return cacheData; }; const networkFirst = async req => { //先发起网络请求,如果失败则再从缓存中匹配 const cache = await caches.open(CACHE_NAME); let fetchResult; try { await Promise.race([requestPromise(req).then(res => { fetchResult = res; if (timer) clearTimeout(timer); if (isNetworkSlowly) isNetworkSlowly = false; hasShowNotification = false; }), timeout_promise()]); if (!fetchResult || fetchResult.status !== 200) return fetchResult; cache.put(req, fetchResult.clone()); return fetchResult; } catch (e) { const cacheData = await cache.match(req); if (navigator.onLine && cacheData && e === 'request timeout' && isNetworkSlowly && !hasShowNotification) { showLocalNotification('网络不给力,当前访问的是缓存数据'); isNetworkSlowly = false; hasShowNotification = true; } console.log(e, cacheData, 'error') return cacheData; } } //设置一定时间,在原本的fetch请求还没有响应的情况下,让service worker中的fetch报出’request timeout‘错误,从而转向向cache中匹配请求资源,实现在弱网情况下的网页正常浏览 const timeout_promise = () => { return new Promise((resolve, reject) => { timer = setTimeout(() => { if (!isNetworkSlowly) isNetworkSlowly = true; reject('request timeout'); }, 9000); }); } const showLocalNotification = (title, body) => { const options = {}; try { self.registration.showNotification(title, options); } catch (error) { console.warn(error); } };
根据自己的需求选择网络优先还是缓存优先,例如
isNetworkFirst = url.origin === self.origin && req.method === 'GET'
,e.respondWith()
对拦截的请求,把缓存匹配的或者网络请求到的数据返回,作出最后的响应。self.registration.showNotification()
向浏览器发送消息。
其他相关
-
缓存使用到的
cacheStorage
可见详情文档 -
浏览器可以通过
addEventListener('offline', () => {})
与addEventListener('online', () => {})
来监听浏览器网络在线与离线状态。但无法判断弱网状态,弱网状态请求缓存数据的具体实现也可根据自己的项目逻辑来定(例如:请求超过10秒还未得到响应,判定为弱网状态)。 -
service worker
注册后,对静态资源的下载缓存会占用部分带宽,影响项目首页的加载速度,可以设置一个定时器,在一定的时间后才启动注册程序。 -
cacheStorage
无法缓存POST
请求的数据。 -
向浏览器发送消息,首先需要获取相应的权限。
permission = await window.Notification.requestPermission()
获取浏览器发送提醒消息权限,permission = 'granted'
时,允许发送消息。 -
使用
self.skipWaiting()
可以保证执行最新的sw,但新旧sw的交替,往往都要经过service worker
的install->waiting->active
,因此总会有页面前后期由不同的sw来处理的问题。
网友评论