使用 Service Workers 來預緩存應用外殼

Progressive Web Apps 是快速且可安裝的,這意味着它能在在線、離線、斷斷續續或者緩慢的網絡環境下使用。爲了實現這個目標,咱們須要使用一個 service worker 來緩存應用外殼,以保證它能始終迅速可用且可靠。css

若是你對 service workers 不熟悉,你能夠經過閱讀 介紹 Service Workers 來了解關於它能作什麼,它的生命週期是如何工做的等等知識。html

service workers 提供的是一種應該被理解爲漸進加強的特性,這些特性僅僅做用於支持service workers 的瀏覽器。好比,使用 service workers 你能夠緩存應用外殼和你的應用所需的數據,因此這些數據在離線的環境下依然能夠得到。若是瀏覽器不支持 service workers ,支持離線的 代碼沒有工做,用戶也能獲得一個基本的用戶體驗。使用特性檢測來漸漸加強有一些小的開銷,它不會在老舊的不支持 service workers 的瀏覽器中產生破壞性影響。web

註冊 service worker

爲了讓應用離線工做,要作的第一件事是註冊一個 service worker,一段容許在後臺運行的腳本,不須要 用戶打開 web 頁面,也不須要其餘交互。shell

這隻須要簡單兩步:數組

  1. 建立一個 JavaScript 文件做爲 service worker
  2. 告訴瀏覽器註冊這個 JavaScript 文件爲 service worker

第一步,在你的應用根目錄下建立一個空文件叫作 service-worker.js 。這個 service-worker.js 文件必須放在跟目錄,由於 service workers 的做用範圍是根據其在目錄結構中的位置決定的。瀏覽器

接下來,咱們須要檢查瀏覽器是否支持 service workers,若是支持,就註冊 service worker,將下面代碼添加至app.js中。緩存

 



if('serviceWorker' in navigator) {  
    navigator.serviceWorker  
        .register('/service-worker.js')  
        .then(function() { console.log('Service Worker Registered'); });  
}

緩存站點的資源

當 service worker 被註冊之後,當用戶首次訪問頁面的時候一個 install 事件會被觸發。在這個事件的回調函數中,咱們可以緩存全部的應用須要再次用到的資源。網絡

當 service worker 被激活後,它應該打開緩存對象並將應用外殼須要的資源存儲進去。將下面這些代碼加入你的service-worker.js (你能夠在your-first-pwapp-master/work中找到) :app

 
var cacheName = 'weatherPWA-step-6-1';
var filesToCache = [];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching app shell');
      return cache.addAll(filesToCache);
    })
  );
});

首先,咱們須要提供一個緩存的名字並利用 caches.open()打開 cache 對象。提供的緩存名容許咱們給 緩存的文件添加版本,或者將數據分開,以致於咱們可以輕鬆地升級數據而不影響其餘的緩存。svg

一旦緩存被打開,咱們能夠調用 cache.addAll() 並傳入一個 url 列表,而後加載這些資源並將響應添加至緩存。不幸的是 cache.addAll() 是原子操做,若是某個文件緩存失敗了,那麼整個緩存就會失敗!

好的。讓咱們開始熟悉如何使用DevTools並學習如何使用DevTools來調試service workers。在刷新你的網頁前,開啓DevTools,從 Application 的面板中打開 Service Worker 的窗格。它應該是這樣的:

當你看到這樣的空白頁,這意味着當前打開的頁面沒有已經被註冊的Service Worker。

如今,從新加載頁面。Service Worker的窗格應該是這樣的:

當你看到這樣的信息,這意味着頁面有個Service Worker正在運行。

如今讓咱們來示範你在使用Service Worker時可能會遇到的問題。爲了演示, 咱們將把service-worker.js裏的install 的事件監聽器的下面添加在activate 的事件監聽器。

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
});
當 service worker 開始啓動時,這將會發射activate事件。

打開DevTools並刷新網頁,切換到應用程序面板的Service Worker窗格,在已被激活的Service Worker中單擊inspect。理論上,控制檯將會出現[ServiceWorker] Activate的信息,但這並無發生。如今回去Service Worker窗格,你會發現到新的Service Worker是在「等待」狀態。

簡單來講,舊的Service Worker將會繼續控制該網頁直到標籤被關閉。所以,你能夠關閉再從新打開該網頁或者點擊 skipWaiting 的按鈕,但一個長期的解決方案是在DevTools中的Service Worker窗格啓用 Update on Reload 。當那個複選框被選擇後,當每次頁面從新加載,Service Worker將會強制更新

啓用 update on reload 複選框並從新加載頁面以確認新的Service Worker被激活。

Note: 您可能會在應用程序面板裏的Service Worker窗格中看到相似於下面的錯誤信息,但你能夠放心的忽略那個錯誤信息。

Ok, 如今讓咱們來完成activate 的事件處理函數的代碼以更新緩存。

self.addEventListener('activate', function(e) {  
  console.log('[ServiceWorker] Activate');  
  e.waitUntil(  
    caches.keys().then(function(keyList) {  
      return Promise.all(keyList.map(function(key) {  
        console.log('[ServiceWorker] Removing old cache', key);  
        if (key !== cacheName) {  
          return caches.delete(key);  
        }  
      }));  
    })  
  );  
});
確保在每次修改了 service worker 後修改 cacheName,這能確保你永遠可以從緩存中得到到最新版本的文件。過一段時間清理一下緩存刪除掉沒用的數據也是很重要的。

最後,讓咱們更新一下 app shell 須要的緩存的文件列表。在這個數組中,咱們須要包括全部咱們的應用須要的文件,其中包括圖片、JavaScript以及樣式表等等。

var filesToCache = [  
  '/',  
  '/index.html',  
  '/scripts/app.js',  
  '/styles/inline.css',  
  '/images/clear.png',  
  '/images/cloudy-scattered-showers.png',  
  '/images/cloudy.png',  
  '/images/fog.png',  
  '/images/ic_add_white_24px.svg',  
  '/images/ic_refresh_white_24px.svg',  
  '/images/partly-cloudy.png',  
  '/images/rain.png',  
  '/images/scattered-showers.png',  
  '/images/sleet.png',  
  '/images/snow.png',  
  '/images/thunderstorm.png',  
  '/images/wind.png'  
];
我麼的應用目前還不能離線工做。咱們緩存了 app shell 的組件,可是咱們仍然須要從本地緩存中加載它們。

從緩存中加載 app sheel

Service workers 能夠截獲 Progressive Web App 發起的請求並從緩存中返回響應。這意味着咱們可以 決定如何來處理這些請求,以及決定哪些網絡響應可以成爲咱們的緩存。

好比:

self.addEventListener('fetch', function(event) {  
  // Do something interesting with the fetch here  
});
讓咱們來從緩存中加載 app shell。將下面代碼加入 service-worker.js 中:

self.addEventListener('fetch', function(e) {  
  console.log('[ServiceWorker] Fetch', e.request.url);  
  e.respondWith(  
    caches.match(e.request).then(function(response) {  
      return response || fetch(e.request);  
    })  
  );  
});
從內至外,caches.match() 從網絡請求觸發的 fetch 事件中獲得請求內容,並判斷請求的資源是 否存在於緩存中。而後以緩存中的內容做爲響應,或者使用 fetch 函數來加載資源(若是緩存中沒有該資源)。 response 最後經過 e.respondWith() 返回給 web 頁面。

測試

你的應用程序如今能夠在離線下使用了! 讓咱們來試試吧!

先刷新那個網頁, 而後去DevTools裏的 Cache Storage 窗格中的 Application 面板上。展開該部分,你應該會在左邊看到您的app shell緩存的名稱。當你點擊你的appshell緩存,你將會看到全部已經被緩存的資源。

如今,讓咱們測試離線模式。回去DevTools中的 Service Worker 窗格,啓用 Offline 的複選框。啓用以後,你將會在 Network 窗格的旁邊看到一個黃色的警告圖標。這表示您處於離線狀態。

刷新網頁,而後你會發現你的網頁仍然能夠正常操做!

下一步驟是修改該應用程序和service worker的邏輯,讓氣象數據可以被緩存,並能在應用程序處於離線狀態,將最新的緩存數據顯示出來。

Tip: 若是你要清除全部保存的數據(localStoarge,IndexedDB的數據,緩存文件),並刪除任何的service worker,你能夠在DevTools中的Application 面板裏的Clear storage清除。


小心邊緣問題

以前提到過,這段代碼 必定不要用在生產環境下 ,由於有不少沒有處理的邊界狀況。

緩存依賴於每次修改內容後更新緩存名稱

好比緩存方法須要你在每次改變內容後更新緩存的名字。不然,緩存不會被更新,舊的內容會一直被緩存返回。 因此,請確保每次修改你的項目後更新緩存名稱。

每次修改後全部資源都須要被從新下載

另外一個缺點是當一個文件被修改後,整個緩存都須要被從新下載。這意味着即便你修改了一個簡單的拼寫錯誤 也會讓整個緩存從新下載。這不過高效。

瀏覽器的緩存可能阻礙 service worker 的緩存的更新

另一個重要的警告。首次安裝時請求的資源是直接經由 HTTPS 的,這個時候瀏覽器不會返回緩存的資源, 除此以外,瀏覽器可能返回舊的緩存資源,這致使 service worker 的緩存不會獲得 更新。

在生產環境中當下 cache-first 策略

咱們的應用使用了優先緩存的策略,這致使全部後續請求都會從緩存中返回而不詢問網絡。優先緩存的策略是 很容易實現的,但也會爲將來帶來諸多挑戰。一旦主頁和註冊的 service worker 被緩存下來,將會很難 去修改 service worker 的配置(由於配置依賴於它的位置),你會發現你部署的站點很難被升級。

我該如何避免這些邊緣問題

咱們該如何避免這些邊緣問題呢? 使用一個庫,好比 sw-precache, 它對資源什麼時候過時提供了 精細的控制,可以確保請求直接經由網絡,而且幫你處理了全部棘手的問題。

實時測試 service workers 提示

調試 service workers 是一件有調整性的事情,當涉及到緩存後,當你指望緩存更新,但實際上它並無的時候,事情更是變得像一場惡夢。在 service worker 典型的生命週期和你的代碼之間,你很快就會受挫。但幸運的是,這裏有一些工具可讓你的生活更加簡單。

其餘的提示:

一旦 service worker 被註銷(unregistered)。它會繼續做用直到瀏覽器關閉。
若是你的應用打開了多個窗口,新的 service worker 不會工做,直到全部的窗口都進行了刷新,使用了 新的 service worker。
註銷一個 service worker 不會清空緩存,因此若是緩存名沒有修改,你可能繼續得到到舊的數據。
若是一個 service worker 已經存在,並且另一個新的 service worker 已經註冊了,這個新的 service worker 不會接管控制權,知道該頁面從新刷新後,除非你使用馬上控制的方式。

 

注:使用例程final或者其餘service worker會出現serviceworker failed to install的錯誤,是由於路徑緣由致使緩存文件沒法加載,請修改js中的文件路徑或者將images 、scripts、styles三個文件夾複製到網站根目錄下。

相關文章
相關標籤/搜索