Workbox 3:Service Worker 可以如此簡單

Workbox 3:Service Worker 可以如此簡單

如果你追求極致的 Web 體驗,你一定在站點中使用過 PWA,也一定面臨過在編寫 Service Worker 代碼時的猶豫不決,因爲 Service Worker 太重要了,一旦註冊在用戶的瀏覽器,全站的請求都會被 Service Worker 控制,一不留神,小問題也成了大問題了。不過到了現在有了 Workbox 3,一切關於 Service Worker 的擔心都不再是問題。

科普 Service Worker

如果你已經熟悉 Service Worker,可以跳過此段。

Service Worker 是 PWA 中重要的一部分,它是一個網站安插在用戶瀏覽器中的大腦。Service Worker 是這樣被註冊在頁面上的:

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}

爲什麼說 SW(下文將 Service Worker 簡稱爲 SW)是網站的大腦?舉個例子,如果在 www.example.com 的根路徑下注冊了一個 SW,那麼這個 SW 將可以控制所有該瀏覽器向 www.example.com 站點發起的請求。只需要監聽 fetch 事件,你就可以任意的操縱請求,可以返回從 CacheStorage 中讀的數據,也可以通過 Fetch API 發起新的請求,甚至可以 new 一個 Response,返回給頁面。

// 一段糟糕的 SW 代碼,在這個 SW 註冊好以後,整個 SW 控制站點的所有請求返回的都將是字符串 "bad",包括頁面的 HTML
self.addEventListener('fetch', function(event) {
event.respondWith(
new Response('bad')
);
});

就是因爲 SW 權利太大了,寫起來纔會如履薄冰,一不小心有些頁面資源就不能及時正確的更新了。

一個還算完整的 Service Worker 示例

先來看一個直接手寫的 SW 文件

var cacheStorageKey = 'cachesName';
var cacheList = [
// 註冊成功後要立即緩存的資源列表
]

// 當瀏覽器解析完 SW 文件時觸發 install 事件
self.addEventListener('install', function(e) {
// install 事件中一般會將 cacheList 中要換存的內容通過 addAll 方法,請求一遍放入 caches 中
e.waitUntil(
caches.open(cacheStorageKey).then(function(cache) {
return cache.addAll(cacheList)
})
);
});

// 激活時觸發 activate 事件
self.addEventListener('activate', function(e) {
// active 事件中通常做一些過期資源釋放的工作,匹配到就從 caches 中刪除
var cacheDeletePromises = caches.keys().then(cacheNames => {
return Promise.all(cacheNames.map(name => {
if (name !== cacheStorageKey) {
return caches.delete(name);
} else {
return Promise.resolve();
}
}));
});

e.waitUntil(
Promise.all([cacheDeletePromises])
);
});

self.addEventListener('fetch', function(e) {
// 在此編寫緩存策略
e.respondWith(
// 可以通過匹配緩存中的資源返回
caches.match(e.request)
// 也可以從遠端拉取
fetch(e.request.url)
// 也可以自己造
new Response('自己造')
// 也可以通過吧 fetch 拿到的響應通過 caches.put 方法放進 caches
);
});

其實所有站點 SW 的 install 和 active 都差不多,無非是做預緩存資源列表,更新後緩存清理的工作,邏輯不太複雜,而重點在於 fetch 事件。上面的代碼,我把 fetch 事件的邏輯省略了,因爲如果認真寫的話,太多了,而且也不利於講明白緩存策略這件事。想象一下,你需要根據不同文件的擴展名把不同的資源通過不同的策略緩存在 caches 中,各種 CSS,JS,HTML,圖片,都需要單獨搞一套緩存策略,你就知道 fetch 中需要寫多少東西了吧。

Workbox 3

Workbox 的出現就是爲了解決上面的問題的,它被定義爲 PWA 相關的工具集合,其實圍繞它的還有一些列工具,如 workbox-cli、gulp-workbox、webpack-workbox-plagin 等等,不過他們都不是今天的重點,今天想聊的就是 Workbox 本身。

其實可以把 Workbox 理解爲 Google 官方的 PWA 框架,它解決的就是用底層 API 寫 PWA 太過複雜的問題。這裏說的底層 API,指的就是去監聽 SW 的 install、active、 fetch 事件做相應邏輯處理等。使用起來是這樣的:

// 首先引入 Workbox 框架
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.3.0/workbox-sw.js');
workbox.precaching([
// 註冊成功後要立即緩存的資源列表
]);

// html的緩存策略
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'
})
);

上面的代碼理解起來就容易的多了,通過 workbox.precaching 中的是 install 以後要塞進 caches 中的內容,workbox.routing.registerRoute 中第一個參數是一個正則,匹配經過 fetch 事件的所有請求,如果匹配上了,就走相應的緩存策略 workbox.strategies 對象爲我們提供了幾種最常用的策略,如下:

Stale-While-Revalidate

Cache First

Network First

Network Only

Cache Only

你可以通過 plugin 擴展這些策略,比如增加個緩存過期時間(官方有提供)什麼的。甚至可以繼續監聽 fetch 事件,然後使用這些策略,官方文檔在 這裏 .

經驗之談

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

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。

最後打個報告

淘寶 PC 首頁的 Service Worker 上線已經有一段時間了,經過不斷地對緩存策略的調整,收益還是比較明顯的,頁面總下載時間從平均 1.7s,下降到了平均 1.4s,縮短了近 18% 的下載時間。

前面的例子中,我們使用的是 Google 的 CDN 地址引入的 Workbox,我已經將 3.3.0 版本遷移到 alicdn,後續還會繼續維護更新,使用方法如下:

importScripts('https://g.alicdn.com/kg/workbox/3.3.0/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: 'https://g.alicdn.com/kg/workbox/3.3.0/'
});

參考文獻

Photo by Peter Wendt on Unsplash

Workbox 3:Service Worker 可以如此簡單

如果你追求極致的 Web 體

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章