借用workbox實現離線快取應用

jannesand發表於2019-03-29

PS: 這是本人的第一篇文章,如有不對或結構不清晰的地方,望指出,我會盡量去完善,謝謝大家!

什麼是workbox,workbox有什麼用途,為什麼要使用它?在介紹workbox之前,我們來先大致瞭解一下service worker,有助於我們後面更好地去workbox。

一. service worker

service worker是在瀏覽器後臺獨立於網頁執行的指令碼,它能夠實現對網路請求進行快取,並向網頁推送和同步資訊的功能,令人更加興奮的是,它可以實現離線的情況下,也能看到我們的網頁,極大提升了我們的使用者體驗。

service worker 已經得到越來越多的瀏覽器的支援,包括蘋果、騰訊的X5核心。蘋果從safari11開始,已經開始支援了。支援情況如下:

image

二. 為什麼要用workbox

workbox 是 GoogleChrome 團隊推出的一套 Web App 靜態資源和請求結果的本地儲存的解決方案,該解決方案包含一些 Js 庫和構建工具,在 Chrome Submit 2017 上首次隆重面世。而在 workbox 背後則是 Service Worker 和 Cache API 等技術和標準在驅動。在 Workebox 之前,GoogleChrome 團隊較早時間推出過 sw-precache 和 sw-toolbox 庫,但是在 GoogleChrome 工程師們看來,workbox 才是真正能方便統一的處理離線能力的更完美的方案,所以停止了對 sw-precache 和 sw-toolbox 的維護。那workbox能解決什麼問題呢?

在service worker中,如果我們要攔截並代理所有的請求,需要我們手動去維護一套快取列表。但是現在前端開發,多數用webpack、gulp、grant來構建前端的程式碼,導致我們的檔名可能會經常發生,這個時候,特別是中大型的多頁應用,快取列表的內容可能會非常多,手動維護就顯得非常麻煩,維護成本也變得很高。

這個時候,workbox的橫空出世,就是為了解決上面的問題。

workbox的一些特性:

  • 不管你的站點是哪種方式構建的,都可以實現離線快取的效果;
  • 自動管理好快取列表,包括更新、同步、刪除舊的快取等;
  • 配置簡單卻不失靈活,可以完全自定義相關需求(支援 Service Worker 相關的特性如 Web Push, Background sync 等)。
  • 針對各種應用場景的多種快取策略。

三. workbox的使用

下面來看下workbox的例子。

  1. 在入口頁面的onload中,註冊一個service worker,註冊時引入快取列表檔案,也就是build.sw.js。

index.html

<script>
// Register A service worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register(`./build.sw.js`)
      .then(function(registration) {
        // Registration was successful
        console.log('[success] register ')
      }, function(err) {
        // registration failed :(
        console.log('[fail]: ', err);
      });
  });
 <script>
}
複製程式碼
  1. 在build.sw.js頁面配置快取列表和快取策略
// 首先引入 Workbox 框架
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.3.0/workbox-sw.js');

// 註冊成功後要立即快取的資源列表
workbox.precaching.precacheAndRoute([
  {
    "url": "css/index.css",
    "revision": "835ba5c3"
  },
  {
    "url": "images/xxx.png",
    "revision": "b1537bfs"
  },
  {
    "url": "index.html",
    "revision": "b331f695"
  },
  {
    "url": "js/index.js",
    "revision": "4d562866"
  }
]);

// 快取策略
workbox.routing.registerRoute(
  new RegExp(''.*\.html'),
  workbox.strategies.networkFirst()
);

workbox.routing.registerRoute(
  new RegExp('.*\.(?:js|css)'),
  workbox.strategies.cacheFirst()
);

workbox.routing.registerRoute(
  new RegExp('https://your\.cdn\.com/'),
  workbox.strategies.staleWhileRevalidate()
);

workbox.routing.registerRoute(
  new RegExp('https://your\.img\.cdn\.com/'),
  workbox.strategies.cacheFirst({
    cacheName: 'example:img'
  })
);
複製程式碼

實現的效果如下:

image

我們來看下build.sw.js檔案的內容,主要包含快取列表和快取策略。這裡面的內容不用我們手動生成,workbox有三種方式生成,我們可以使用workbox-webpack-plugin、workbox-cli、workbox-build。我們暫不討論具體的實現,在這裡,我們先來了解一下預快取列表和快取策略這兩個東西。

預快取列表

如果我們要快取靜態資源,平時不會經常更新,只有到發版時才會修改了資源的hash值,才需要重新更新的,那那 precache 預快取應該是你所期待的。

workbox 提供了一種非常方便的 API 幫助我們解決 precache 的問題,我們可以使用workbox.precaching來配置,配置格式如下:

workbox.precaching.precacheAndRoute([
  {
    "url": "將要預快取的檔案 URL",
    "revision": "快取的hash值"
  },
])
複製程式碼

路由請求快取

路由請求快取是指通過對匹配路由給檔案採取不用的快取方式,這個可以通過workbox.routing.registerRoute來進行配置。 路由匹配的方式有三種:

  1. 通過字串的方式進行匹配
// 可以直接是當前專案下的絕對路徑
workbox.routing.registerRoute(
    'path/to/logo.png',
    handler // handler 是做快取策略的回撥函式,通常指後面所會降到的 '快取策略函式'
);

// 也可以是完整的帶完整 host 的 URL 路徑,這裡的 URL 必須是 https 的
workbox.routing.registerRoute(
    'https://example.com/a/b/c.jpg',
    handler
);
複製程式碼
  1. 通過正則的方式進行匹配
workbox.routing.registerRoute(
    new RegExp('.*\.(js|css|jpg|png|gif)'), // 這裡是任何正則都行,只要能匹配得上的請求路由地址
    handler
);
複製程式碼

3.通過回撥函式的方式進行匹配

// 通過回撥函式來匹配請求路由將會讓策略更加靈活
const customFun = ({url, event}) => {
    // 如果請求路由匹配了就返回true,也可以返回一個引數物件以供 handler 接收處理
    return false;
};

workbox.routing.registerRoute(
    customFun,
    handler
);
複製程式碼

快取策略

快取策略是指對於匹配到的路由,採取何種方式進行快取。 workbox提供了兩種配置快取策略的方式

  • 通過 workbox.strategies API 提供的 快取策略。
  • 提供一個自定義返回帶有返回結果的 Promise 的回撥方法。

以下介紹workbox預設提供的幾種快取策略,包含有五種,分別是:

  • Stale While Revalidate
  • Network First
  • Cache First
  • Network Only
  • Cache Only

Stale While Revalidate

這種策略的意思是當請求的路由有對應的 Cache 快取結果就直接返回,在返回 Cache 快取結果的同時會在後臺發起網路請求拿到請求結果並更新 Cache 快取,如果本來就沒有 Cache 快取的話,直接就發起網路請求並返回結果。 使用方式如下:

workbox.routing.registerRoute(
    match, // 匹配的路由
    workbox.strategies.staleWhileRevalidate()
);
複製程式碼

Network First

這種策略就是當請求路由是被匹配的,就採用網路優先的策略,也就是優先嚐試拿到網路請求的返回結果,如果拿到網路請求的結果,就將結果返回給客戶端並且寫入 Cache 快取,如果網路請求失敗,那最後被快取的 Cache 快取結果就會被返回到客戶端 使用方式如下:

workbox.routing.registerRoute(
    match, // 匹配的路由
    workbox.strategies.networkFirst()
);
複製程式碼

Cache First

這個策略的意思就是當匹配到請求之後直接從 Cache 快取中取得結果,如果 Cache 快取中沒有結果,那就會發起網路請求,拿到網路請求結果並將結果更新至 Cache 快取,並將結果返回給客戶端。

workbox.routing.registerRoute(
    match, // 匹配的路由
    workbox.strategies.cacheFirst()
);
複製程式碼

Network Only

比較直接的策略,直接強制使用正常的網路請求,並將結果返回給客戶端,這種策略比較適合對實時性要求非常高的請求。

workbox.routing.registerRoute(
    match, // 匹配的路由
    workbox.strategies.networkOnly()
);
複製程式碼

Cache Only

這個策略也比較直接,直接使用 Cache 快取的結果,並將結果返回給客戶端,這種策略比較適合一上線就不會變的靜態資源請求。

workbox.routing.registerRoute(
    match, // 匹配的路由
    workbox.strategies.cacheOnly()
);
複製程式碼

四. 使用workerbox後的效果

在我們的專案中,我們以DomContentLoaded的時間作為參考點,對比有加service worker 和未加的service worker情況。

測試條件

以首頁為例,在不同的網路環境下,發起10次網路請求,然後取平均值,作為它們的最終結果,測試結果如下:

image
通過上面的資料可以得出幾個結論:

  • 在弱環境下,service worker的優勢越發明顯,
  • 即使在wifi環境下面,由於存在快取的情況,瀏覽器載入的速度也比未使用service worker的時間要短。
  • 在無網路環境的情況,也可以做到離線快取的效果,極大地提升頁面的使用者體驗。

五. 幾個注意點

在使用workbox的過程中,會遇到一些問題,下面列出幾點,也算是做個總結:

1. service worker 註冊檔案放置的位置

在頁面註冊service worker的時候,儘量註冊到專案的根目錄下,這樣才能最大的發揮service worker的作用

// build.sw.js最好放在專案的根目錄下,才能發揮最大的快取效果
navigator.serviceWorker.register(`./build.sw.js`)

// 如果這樣配置的話,就只有path目錄下面的檔案才能實現快取,其他目錄,包括根目錄的都不能快取
navigator.serviceWorker.register(`./path/build.sw.js`)
複製程式碼

2.使用workbox 命令列生成預快取列表的注意點

我們先預設一下應用場景:假設你的專案在目錄 /app 下,必須保證在你的專案根目錄下有一個 app/sw.js 包含以下內容:

// 通常專案中的 sw.js 原始檔都是通過這樣預留一個空陣列的方式來預快取內容列表的
workbox.precaching.precacheAndRoute([]);
複製程式碼

這樣才能保證能將生成的預快取內容列表內容注入到 Service Worker 檔案中。

3.快取策略設定

在經過一段時間的使用和思考以後,給出我認為最為合理,最為保守的快取策略。

HTML,如果你想讓頁面離線可以訪問,使用 NetworkFirst,如果不需要離線訪問,使用 NetworkOnly,其他策略均不建議對 HTML 使用。

CSS 和 JS,情況比較複雜,因為一般站點的 CSS,JS 都在 CDN 上,SW 並沒有辦法判斷從 CDN 上請求下來的資源是否正確(HTTP 200),如果快取了失敗的結果,問題就大了。這種我建議使用 Stale-While-Revalidate 策略,既保證了頁面速度,即便失敗,使用者重新整理一下就更新了。

如果你的 CSS,JS 與站點在同一個域下,並且檔名中帶了 Hash 版本號,那可以直接使用 Cache First 策略。

圖片建議使用 Cache First,並設定一定的失效事件,請求一次就不會再變動了。

上面這些只是普適性的策略,見仁見智。

還有,要牢記,對於不在同一域下的任何資源,絕對不能使用 Cache only 和 Cache first。

4.service worker的執行環境

需要注意的是,Service Worker 指令碼除了域名為 localhost 時能執行在 http 協議下以外,只能執行 https 協議下。

5. 使用Service Worker快取請求時,POST請求無法快取

Google對web的標準化還是遵循的,SW認為POST請求就是象伺服器提交資源,不存在快取需求


參考文件:

developers.google.com/web/tools/w…

zoumiaojiang.com/article/ama…

相關文章