PWA
Service worker
是PWA
得以實現(xiàn)的核心技術(shù)
service worker
Service worker
是一個獨立的worker
線程廷雅,獨立于當前網(wǎng)頁進程,是一種特殊的web worker
京髓。主要功能在生命周期函數(shù)中實現(xiàn)航缀。
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
獲取所有目錄文件名堰怨,便于做靜態(tài)資源的緩存芥玉。
PWA用到的service worker生命周期函數(shù)
-
install緩存所有你需要的靜態(tài)資源
self.addEventListener('install', async () => { console.log('service worker installing'); const cache = await caches.open(CACHE_NAME); //cacheStorage中緩存的名稱 const CACHE_URL = global.serviceWorkerOptions.assets.concat(['/']); //緩存的靜態(tài)資源目錄,不要忘記緩存'/'目錄文件备图,在斷網(wǎng)情況下飞傀,頁面首先加載的是'/'目錄資源皇型。 await cache.addAll(CACHE_URL); //此處只要有一個資源無法下載,靜態(tài)資源的緩存都會失敗 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可以攔截所有的請求,并做數(shù)據(jù)緩存
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)); //網(wǎng)絡(luò)優(yōu)先 } else if (isCahceFirst) { e.respondWith(cacheFirst(req));//緩存優(yōu)先 } }) const cacheFirst = async req => { //先從緩存中獲取數(shù)據(jù)绞吁,如果沒有匹配到幢痘,再發(fā)起網(wǎng)絡(luò)請求 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 => { //先發(fā)起網(wǎng)絡(luò)請求,如果失敗則再從緩存中匹配 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('網(wǎng)絡(luò)不給力家破,當前訪問的是緩存數(shù)據(jù)'); isNetworkSlowly = false; hasShowNotification = true; } console.log(e, cacheData, 'error') return cacheData; } } //設(shè)置一定時間颜说,在原本的fetch請求還沒有響應(yīng)的情況下,讓service worker中的fetch報出’request timeout‘錯誤汰聋,從而轉(zhuǎn)向向cache中匹配請求資源门粪,實現(xiàn)在弱網(wǎng)情況下的網(wǎng)頁正常瀏覽 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); } };
根據(jù)自己的需求選擇網(wǎng)絡(luò)優(yōu)先還是緩存優(yōu)先,例如
isNetworkFirst = url.origin === self.origin && req.method === 'GET'
烹困,e.respondWith()
對攔截的請求玄妈,把緩存匹配的或者網(wǎng)絡(luò)請求到的數(shù)據(jù)返回,作出最后的響應(yīng)髓梅。self.registration.showNotification()
向瀏覽器發(fā)送消息拟蜻。
其他相關(guān)
緩存使用到的
cacheStorage
可見詳情文檔瀏覽器可以通過
addEventListener('offline', () => {})
與addEventListener('online', () => {})
來監(jiān)聽瀏覽器網(wǎng)絡(luò)在線與離線狀態(tài)。但無法判斷弱網(wǎng)狀態(tài)枯饿,弱網(wǎng)狀態(tài)請求緩存數(shù)據(jù)的具體實現(xiàn)也可根據(jù)自己的項目邏輯來定(例如:請求超過10秒還未得到響應(yīng)酝锅,判定為弱網(wǎng)狀態(tài))。service worker
注冊后奢方,對靜態(tài)資源的下載緩存會占用部分帶寬搔扁,影響項目首頁的加載速度,可以設(shè)置一個定時器蟋字,在一定的時間后才啟動注冊程序稿蹲。cacheStorage
無法緩存POST
請求的數(shù)據(jù)。向瀏覽器發(fā)送消息愉老,首先需要獲取相應(yīng)的權(quán)限场绿。
permission = await window.Notification.requestPermission()
獲取瀏覽器發(fā)送提醒消息權(quán)限,permission = 'granted'
時嫉入,允許發(fā)送消息焰盗。使用
self.skipWaiting()
可以保證執(zhí)行最新的sw,但新舊sw的交替咒林,往往都要經(jīng)過service worker
的install->waiting->active
熬拒,因此總會有頁面前后期由不同的sw來處理的問題。