[譯]前端離線指南(上)

司南free發表於2018-12-05

原文連結:The offline cookbook 作者:Jake Archibald

[譯]前端離線指南(下)

使用AppCache可以為我們提供幾種支援內容離線工作的模式。如果這些模式正是你所需要的,那麼恭喜你,你中了APPCache的大獎(儘管頭等獎依然無人認領),但我們這些其餘的人都擠在角落裡來回搖擺(譯者注:作者指的是由於設計上的原因,AppCache逐漸地被Web標準移除,雖然現在依然有瀏覽器支援這個功能,但最好不要再使用它了)

對於ServiceWorker(介紹),我們放棄嘗試去解決離線問題,並且給開發者們提供靈活的元件從而讓他們自己去解決離線問題。它為您提供了控制快取和處理請求的方式。這就意味著您可以建立您自己的模式。接下來讓我們來看一下幾個隔離環境下的可行模式,但是在實踐中,您可能會根據URL和context以串聯方式用到其中的多個模式。

目前,除非另有說明,所有的示例程式碼都可以執行在Chrome和Firefox瀏覽器中。關於ServiceWorker支援程度的完整詳情,請查閱"Is Service Worker Ready?"

有關對於其中部分模式的執行演示,請查閱Trained-to-thrill,並且此處的視訊將向您展示效能影響。

快取機-何時開始儲存資源?

您可以通過ServiceWorker來獨立地從快取中處理請求,所以我們要先單獨地研究一下它們。首先,我們啥時候應該進行快取呢?

安裝時——以依賴的形式

[譯]前端離線指南(上)

ServiceWorker提供給您一個install事件,您可以使用它把資源準備好,即在處理其他事件之前必須要提前準備好的東西。但是當這些操作正在進行中的時候,任何舊版本的ServiceWorker仍舊在執行並且提供給頁面,因此您在此處進行的操作一定不能中斷它們。

適用於: CSS、圖片、字型、JS檔案、模板等,基本包含了你認為網站在當前“版本”中應該需要的所有靜態資源。

如果未能獲取上述資源,那麼您的網站完全無法執行,對應的本機應用會將這些物件包含在初始下載中。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
        // etc
      ]);
    })
  );
});
複製程式碼

event.waitUntil接受一個promise物件作引數,來定義安裝時長和安裝是否成功,如果promise狀態為rejected,則認為此次安裝失敗,並且拋棄ServiceWorker(如果一箇舊版本的ServiceWorker正在執行,則它將保持不變)。caches.opencaches.addAll都返回promise物件,如果其中有任何一個資源獲取失敗,則caches.addAll會呼叫reject。 在trained-to-thrill 上,我使用此方法快取靜態資源

安裝時——不作為依賴

[譯]前端離線指南(上)

此方式與上述相似,但區別是:即使快取失敗,既不會延遲安裝也不會導致安裝失敗。

適用於: 體積較大的,且暫時用不到的資源,比如用於遊戲的較高階別的資源。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function(cache) {
      cache.addAll(
        // levels 11-20
      );
      return cache.addAll(
        // core assets & levels 1-10
      );
    })
  );
});
複製程式碼

我們沒有將levels 11-20的cache.addAll promise物件,返回給event.waitUntil,所以事件即使失敗,遊戲在離線的時候依然可以使用。當然,您必須考慮到缺少這些level的情況,如果缺少它們,則嘗試重新快取它們。

在當level 11-20正在下載的時候,ServiceWorker可能會終止,因為它已經完成處理事件。這就意味著它們就不會被快取下來。未來,我們計劃新增一個在後臺下載的API以處理類似這樣的情況,以及下載像電影一樣的大體積檔案。

啟用時

適用於: 清理和遷移

[譯]前端離線指南(上)

在新的ServiceWorker已經被安裝,並且較早版本的sw沒有在使用的情況下,則新的ServiceWorker會被啟用,您就會得到一個activate事件。由於舊版本的退出,所以此時非常適合處理 IndexedDB 中的架構遷移和刪除未使用的快取。

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // 如果您想刪除快取,則返回true,
          // 但是請記住快取在該域名內的所有頁面之間
          // 是共享的
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
});
複製程式碼

在啟用的過程中,諸如fetch等事件會被放置在一個佇列中,所以一個長時間的啟用可能會阻塞頁面載入。保證您的啟用儘可能地簡潔,僅用於舊版本處於活動狀態時無法執行的操作。

trained-to-thrill上,我使用此方法移除舊快取

在使用者互動時

[譯]前端離線指南(上)

適用於: 如果整個站點無法離線工作,您可以允許使用者選擇需要離線的可用內容,比如,YouTube上的某個視訊,維基百科上的某篇文章,Flickr上的某張圖片等等。

為使用者提供一個“稍後閱讀”或者“離線儲存”的按鈕。當點選按鈕,從網路中獲取您所需要的內容並把它放進快取中。

document.querySelector('.cache-article').addEventListener('click', function(event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function(cache) {
    fetch('/get-article-urls?id=' + id).then(function(response) {
      // /get-article-urls returns a JSON-encoded array of
      // resource URLs that a given article depends on
      return response.json();
    }).then(function(urls) {
      cache.addAll(urls);
    });
  });
});
複製程式碼

cacheAPI在頁面既可以在ServiceWorker中獲取到,也可以在頁面中獲取到,這就意味著你不必一定要通過ServiceWorker來向快取中新增內容。

網路響應時

[譯]前端離線指南(上)

適用於: 頻繁更新的資源,比如使用者收件箱,或者文章內容。同樣適用於不重要但需要謹慎處理的內容,比如使用者頭像。

如果請求的資源與快取中的任何資源均不匹配,則從網路中獲取,將其傳送到頁面中,同時將其新增到快取中。

如果您針對一系列網址執行此操作,如頭像,那麼您需要謹慎,不要使域名下的儲存變得臃腫,如果使用者需要回收磁碟空間,您不會想成為主要候選物件。請確保將快取中不再需要的專案刪除。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function (response) {
        return response || fetch(event.request).then(function(response) {
          cache.put(event.request, response.clone());
          return response;
        });
      });
    })
  );
});
複製程式碼

為了高效使用記憶體,只允許讀取一次responserequestbody,在上面的程式碼中,使用.clone來建立能夠單獨地讀取資料的額外副本。

trained-to-thrill上,我使用此方法快取Flickr影象

Stale-while-revalidate

[譯]前端離線指南(上)

適用於: 頻繁更新,但卻沒必要獲取最新的資源。使用者頭像就屬於此類。

如果快取中已經有一個可用的版本,直接使用該版本,但是會為了下一次的請求而獲取一個更新版本。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});
複製程式碼

它和 HTTP 的 stale-while-revalidate 非常相似。

推送訊息時

[譯]前端離線指南(上)

注意: Chrome暫時還不支援Push。(譯者注:Chrome 50及之後的版本開始支援,更多資訊請參考 can i use

Push API是基於ServiceWorker構建的另一個功能。它允許喚醒ServiceWorker以響應來自系統服務的訊息,即使使用者沒有為您的站點開啟一個標籤,Push API也同樣可以工作。只有ServiceWorker被喚醒。您從頁面請求執行此操作許可權的同時,使用者也將收到提示。

適用於: 與通知有關的內容,比如聊天訊息,突發新聞,或者Email等。同樣適用於不經常更改的可立即同步的內容,例如待辦事項更新或者日程表的更改。

使用者常見的頁面表現是,出現一個通知,當點選它的時候,會開啟或者聚焦到相關的頁面,但是在點選它之前,務必要更新快取。顯然,使用者在收到推送訊息的時候,一定是線上的,但是,當他們最終與通知互動時可能已經離線,因此,允許離線訪問此內容非常重要。Twitter原生應用在大多數情況下都是非常好的離線優先例子,但在這點上卻有點小問題。

如果沒有網路連線,Twitter無法提供與推送訊息相關的內容。但是點按通知會移除通,從而使使用者獲取的資訊比點按通知之前還要少。不要這麼做!

下面的程式碼會在展示通知之前更新快取。

self.addEventListener('push', (event) => {
  if (event.data.text() == 'new-email') {
    event.waitUntil(async function() {
      const cache = await caches.open('mysite-dynamic');
      const response = await fetch('/inbox.json');
      await cache.put('/inbox.json', response.clone());
      const emails = await response.json();
      registration.showNotification("New email", {
        body: "From " + emails[0].from.name
        tag: "new-email"
      });
    }());
  }
});

self.addEventListener('notificationclick', function(event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});
複製程式碼

後臺同步時

[譯]前端離線指南(上)

注意: 後臺同步尚未加入到Chrome穩定版本中。(譯者注:Chrome 49及之後的版本中開始支援,但FireFox、Safari尚未支援,更多資訊請參考 can i use

後臺同步是基於ServiceWorker來構建的另一個功能。它允許您一次性地,或者按照(非常具有啟發性的)時間間隔來請求後臺資料同步。即使使用者沒有為您的站點開啟一個標籤,後臺同步也同樣可以工作。只有ServiceWorker被喚醒。您從頁面請求執行此操作許可權的同時,使用者也將收到提示。

適用於: 不緊急的更新,尤其是那些定期進行的更新,每次更新都傳送一個推送訊息顯得太頻繁,比如社交時間表和新聞資訊。

請繼續閱讀: [譯]前端離線指南(下)

相關文章