使用傳統前端技術增強客戶端快取能力

蘇洋發表於2019-03-01

本文使用「署名 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分鐘(除錯模式單位替換為秒)為一個時間段,為短時間快取的資源進行快取。設定星期數為其他資源進行快取週期。

這裡我們不必過分處理 installactive 事件,只需要統一在 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 v50FireFox v46 以下的客戶端,那麼你可能還需要引入下面的 Polyfill : github.com/dominiccoon…

當然,在引入的時候,建議還是進行壓縮,進一步提高頁面效能(哪怕它是獨立於客戶端指令碼另外的非同步程式)。

當一切都完成之後,如果順利的話,你的網站的將支援有限時間(本文是10min)的離線訪問,以及重複訪問時更好的響應能力。

另外,你也不需要擔心 sw.js 被快取後,這個站點變成“完全離線”。因為 sw.js 按照規範可以保證它的最長存在生命週期是 1天,也就是說,未來你的策略更新最多延遲1天。

最後

使用傳統前端技術增強客戶端快取能力
支援本地快取後的評分

再次跑分,發現 Lighthouse 已經全面綠色評價了,不過前文中我提過,我不太需要把網站變為純粹的 PWA 應用,沒有去設定 mainfest ,所以,依舊有 4 點改進建議。

  1. 使用者不能夠“安裝” Web App。
  2. 沒有定義啟動螢幕(仿客戶端體驗)。
  3. 沒有定義頂欄的主題色。
  4. viewport 沒有優化。

這些後面再說吧。

— EOF


我現在有一個小小的折騰群,裡面聚集了一些喜歡折騰的小夥伴。

在不發廣告的情況下,我們在裡面會一起聊聊軟體、HomeLab、程式設計上的一些問題,也會在群裡不定期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼新增好友。(請註明來源和目的,否則不會通過稽核)
關於折騰群入群的那些事

關於折騰群入群的那些事

相關文章