本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或重新修改使用,但需要註明來源。 署名 4.0 國際 (CC BY 4.0)
本文作者: 蘇洋
建立時間: 2018年10月16日
統計字數: 3721字
閱讀時間: 8分鐘閱讀
本文連結: soulteary.com/2018/10/16/…
使用傳統前端技術增強客戶端快取能力
前幾天重構之後,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
: github.com/dominiccoon…
當然,在引入的時候,建議還是進行壓縮,進一步提高頁面效能(哪怕它是獨立於客戶端指令碼另外的非同步程式)。
當一切都完成之後,如果順利的話,你的網站的將支援有限時間(本文是10min)的離線訪問,以及重複訪問時更好的響應能力。
另外,你也不需要擔心 sw.js
被快取後,這個站點變成“完全離線”。因為 sw.js
按照規範可以保證它的最長存在生命週期是 1天,也就是說,未來你的策略更新最多延遲1天。
最後
再次跑分,發現 Lighthouse
已經全面綠色評價了,不過前文中我提過,我不太需要把網站變為純粹的 PWA
應用,沒有去設定 mainfest
,所以,依舊有 4 點改進建議。
- 使用者不能夠“安裝” Web App。
- 沒有定義啟動螢幕(仿客戶端體驗)。
- 沒有定義頂欄的主題色。
- viewport 沒有優化。
這些後面再說吧。
— EOF
我現在有一個小小的折騰群,裡面聚集了一些喜歡折騰的小夥伴。
在不發廣告的情況下,我們在裡面會一起聊聊軟體、HomeLab、程式設計上的一些問題,也會在群裡不定期的分享一些技術沙龍的資料。
喜歡折騰的小夥伴歡迎掃碼新增好友。(請註明來源和目的,否則不會通過稽核)
關於折騰群入群的那些事