使用傳統前端技術增強客戶端快取能力
前幾天重構之後,Lighthouse 中有一個評分讓我念念不忘: Progressive Web App
。
PWA
不算一個新話題了,所以概念性的東西和 API
我就不多做介紹,下面簡單介紹一個無干預更新的快取方案,整體程式碼量在一百行以內,如果你也想在不“大動干戈”的情況下對站點或者 Web App
進行效能提升的話,可以瞭解一下。
瀏覽器客戶端程式碼
說到 PWA
,我們能直接想到的,無非是 增強快取
和 推送能力
。而這兩個能力,都是 ServiceWorker
API 實現的。(新增桌面圖示這個需求,我不需要,就不介紹了,感興趣可以自行搜尋)
我在之前的重構文章中有簡單聊過訪客資料,其中有一部分訪客使用的客戶端並不支援 Service Worker
,所以在使用它的時候,需要使用 能力探測 的方式引入,比如:
try { ('serviceWorker' in navigator) && navigator.serviceWorker.register('/sw.js'); } catch (error) { console.error(error); }
當然,構建壓縮之後,你得到的結果應該是 drop console
的最簡程式碼。不過,如果你不確定你的執行環境是否有問題,可以使用下面帶有除錯日誌的版本。
try { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.min.js').then(function(registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { console.error('ServiceWorker registration failed: ', err); }); } } catch (error) { console.error(error); }
ServiceWorker 客戶端程式碼
上面介紹瀏覽器客戶端程式碼的時候,有引入一個外部指令碼依賴 sw.js
。
在分享程式碼之前,有做過 PWA
相關專案或者瞭解的同學,可能或多或少會在 增強快取
這個地方被坑到,比如:快取無法更新、快取內容過多無法寫入。
快取無法更新有一個簡單有效的解決方案:定時切換快取使用的 Store
。如果再引入 當前時間 這個因素,可以保障快取使用的 Store
不存在資源爭搶的問題。
結合站點內容特點,配合定期清理快取的指令碼,可以將快取數量控制在一定的範圍之內。
這裡提供一個小思路,對服務端資源進行二次快取的時候,可以設定一個最大快取時間的策略,而這裡有兩個方案:
TTL TTL
我個人選擇第二個方案,犧牲一定的快取複用,但是有效降低資源之間的版本管理的複雜度。
而需要快取的資源一般分為兩類:
- 短時間快取:頁面或者頁面片段
- 相對長時間快取:圖片等媒體資源,或者有一定跨頁面通用能力的指令碼和樣式資源
這裡以10分鐘(除錯模式單位替換為秒)為一個時間段,為短時間快取的資源進行快取。設定星期數為其他資源進行快取週期。
這裡我們不必過分處理 install
和 active
事件,只需要統一在 fetch
事件中進行快取更新和清理即可,比如下面這樣:
function weekId() { var now = (new Date()); var dt = new Date(now.getFullYear(), 0, 1); return Math.ceil((((now - dt) / 86400000) + dt.getDay() + 1) / 7); } var isDevMode = false; var CACHE_LONG_TTL = 'WEEK_' + weekId(); var CACHE_TINY_TTL = 'TINY_'; function cleanCache(whiteList) { return caches.keys().then(function(buckets) { return Promise.all(buckets.filter(function(bucket) { return whiteList.indexOf(bucket) === -1; }).map(function(bucket) { return caches.delete(bucket); })); }); } self.addEventListener('activate', function(event) { event.waitUntil(cleanCache([CACHE_LONG_TTL])); }); self.addEventListener('fetch', function(event) { var url = new URL(event.request.url); if (url.protocol.toLowerCase() !== 'https:') return; var now = new Date; var CACHE_INTERVAL = Math.floor((isDevMode ? now.getSeconds() : now.getMinutes()) / 10); var CACHE_KEY = CACHE_TINY_TTL + CACHE_INTERVAL; if (url.pathname.endsWith('/') || url.pathname.endsWith('.html') || url.pathname.endsWith('.md') || url.pathname.endsWith('/feed/') || url.pathname.endsWith('/feed') || url.pathname.endsWith('sw.js')) { cleanCache([CACHE_LONG_TTL, CACHE_KEY]); event.respondWith(caches.open(CACHE_KEY).then(function(cache) { return cache.match(url).then(function(response) { if (response) return response; return fetch(event.request).then(function(response) { cache.put(event.request, response.clone()); return response; }); }); })); } else { event.respondWith(caches.open(CACHE_LONG_TTL).then(function(cache) { return cache.match(event.request).then(function(response) { if (response) return response; return fetch(event.request).then(function(response) { cache.put(event.request, response.clone()); return response; }); }); })); } });
如果你的訪客有不少是 Chrome v50
和 FireFox v46
以下的客戶端,那麼你可能還需要引入下面的 Polyfill
: ofollow,noindex" target="_blank">https://github.com/dominiccooney/cache-polyfill/blob/master/index.js
當然,在引入的時候,建議還是進行壓縮,進一步提高頁面效能(哪怕它是獨立於客戶端指令碼另外的非同步程序)。
當一切都完成之後,如果順利的話,你的網站的將支援有限時間(本文是10min)的離線訪問,以及重複訪問時更好的響應能力。
另外,你也不需要擔心 sw.js
被快取後,這個站點變成“完全離線”。因為 sw.js
按照規範可以保證它的最長存在生命週期是 1天,也就是說,未來你的策略更新最多延遲1天。
最後
再次跑分,發現 Lighthouse
已經全面綠色評價了,不過前文中我提過,我不太需要把網站變為純粹的 PWA
應用,沒有去設定 mainfest
,所以,依舊有 4 點改進建議。
- 使用者不能夠“安裝” Web App。
- 沒有定義啟動螢幕(仿客戶端體驗)。
- 沒有定義頂欄的主題色。
- viewport 沒有優化。
這些後面再說吧。
— EOF