Workbox-Window v4.x 中文版
Workbox 目前發了一個大版本,從 v3.x 到了 v4.x,變化有挺大的,下面是在 window 環境下的模組。
什麼是 workbox-window?
workbox-window
包是一組模組,用於在 window
上下文中執行,也就是說,在你的網頁內部執行。 它們是 servicewoker
中執行的其他 workbox
的補充。
workbox-window
的主要功能/目標是:
- 通過幫助開發人員確定
serviceWorker
生命週期中最關鍵的時刻,並簡化對這些時刻的響應,簡化serviceWoker
註冊和更新的過程。 - 幫助防止開發人員犯下最常見的錯誤。
- 使
serviceWorker
程式中執行的程式碼與window
中執行的程式碼之間的通訊更加輕鬆。
匯入和使用 workbox-window
workbox-window
包的主要入口點是 Workbox
類,你可以從我們的CDN或使用任何流行的 JavaScript 打包工具將其匯入程式碼中。
使用我們的 CDN
在您的網站上匯入 Workbox
類的最簡單方法是從我們的 CDN:
<script type="module"> import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-window.prod.mjs'; if ('serviceWorker' in navigator) { const wb = new Workbox('/sw.js'); wb.register(); } </script> 複製程式碼
注意,此示例使用 <script type ="module">
和 import
語句來載入 Workbox
類。 雖然您可能認為需要轉換此程式碼以使其在舊版瀏覽器中執行,但實際上並不是必需的。
支援 serviceWorker
的所有主要瀏覽器也支援JavaScript 模組,因此將此程式碼提供給任何瀏覽器都是完美的(舊版瀏覽器將忽略它)。
通過 JavScript 打包載入 Workbox
雖然使用 Workbox
絕對不需要工具,但如果您的開發基礎架構已經包含了與 npm
依賴項一起使用的 webpack
或 Rollup
等打包工具,則可以使用它們來載入 Workbox
。
第一步就是安裝 Workbox
做為你應用的依賴:
npm install workbox-window 複製程式碼
然後,在您的某個應用程式的 JavaScript
檔案中,通過引用 workbox-window
包名稱匯入 Workbox
:
import {Workbox} from 'workbox-window'; if ('serviceWorker' in navigator) { const wb = new Workbox('/sw.js'); wb.register(); } 複製程式碼
如果您的打包工具支援通過動態 import
語句進行程式碼拆分,你還可以有條件地載入 workbox-window
,這有助於減少頁面主包的大小。
儘管 Workbox
非常小(1kb gzip壓縮),但是沒有理由需要載入站點的核心應用程式邏輯,因為 serviceWorker
本質上是漸進式增強。
if ('serviceWorker' in navigator) { const {Workbox} = await import('workbox-window'); const wb = new Workbox('/sw.js'); wb.register(); } 複製程式碼
高階打包概念
與在 Service worker
中執行的 Workbox
包不同, workbox-window
在 package.json
中的 main
和 module
欄位引用的構建檔案被轉換為 ES5
。 這使它們與當今的構建工具相容 - 其中一些不允許開發人員轉換其 node_module
依賴項的任何內容。
如果你的構建系統允許您轉換依賴項(或者如果您不需要轉換任何程式碼),那麼最好匯入特定的原始檔而不是包本身。
以下是你可以匯入 Workbox
的各種方法,以及每個方法將返回的內容的說明:
// 使用ES5語法匯入UMD版本 // (pkg.main: "build/workbox-window.prod.umd.js") const {Workbox} = require('workbox-window'); // 使用ES5語法匯入模組版本 // (pkg.module: "build/workbox-window.prod.es5.mjs") import {Workbox} from 'workbox-window'; // 使用ES2015 +語法匯入模組原始檔 import {Workbox} from 'workbox-window/Workbox.mjs'; 複製程式碼
重要! 如果您直接匯入原始檔,則還需要配置構建過程以縮小檔案,並在將其部署到生產時刪除僅開發程式碼。 有關詳細資訊,請參閱 使用打包(webpack / Rollup)和Workbox的指南 。
示例
匯入 Workbox 類後,可以使用它來註冊 serviceWorker 並與之互動。 以下是您可以在應用程式中使用 Workbox 的一些示例:
註冊 serviceWorker 並在 serviceWorker 第一次處於 active 狀態時通知使用者:
許多 Web 應用程式使用者 serviceWorker 預快取資源,以便其應用程式在後續頁面載入時離線工作。在某些情況下,通知使用者該應用程式現在可以離線使用是有意義的。
const wb = new Workbox('/sw.js'); wb.addEventListener('activated', (event) => { // 如果另一個版本的 serviceWorker,`event.isUpdate`將為true // 當這個版本註冊時,worker 正在控制頁面。 if (!event.isUpdate) { console.log('Service worker 第一次啟用!'); // 如果您的 serviceWorker 配置為預快取資源,那麼 // 資源現在都應該可用。 } }); // 新增事件偵聽器後註冊 serviceWorker 。 wb.register(); 複製程式碼
如果 serviceWorker 已安裝但等待啟用,則通知使用者
當由現有 serviceWorker 控制的頁面註冊新的 serviceWorker 時,預設情況下,在初始 serviceWorker 控制的所有客戶端完全解除安裝之前,serviceWorker 將不會啟用。
這是開發人員常見的混淆源,特別是在重新載入當前頁面不會導致新 serviceWorker 程式啟用的情況下。
為了幫助減少混淆並在發生這種情況時明確說明,Workbox 類提供了一個可以監聽的等待事件:
const wb = new Workbox('/sw.js'); wb.addEventListener('waiting', (event) => { console.log(`已安裝新的 serviceWorker,但無法啟用` + `直到運行當前版本的所有選項卡都已完全解除安裝。`); }); // 新增事件偵聽器後註冊 service worker 。 wb.register(); 複製程式碼
從 workbox-broadcast-update 包通知使用者快取更新
workbox-broadcast-update 包非常棒
能夠從快取中提供內容(快速交付)的方式,同時還能夠通知使用者該內容的更新(使用stale-while-revalidate 策略)。
要從 window 接收這些更新,您可以偵聽 CACHE_UPDATE 型別的訊息事件:
const wb = new Workbox('/sw.js'); wb.addEventListener('message', (event) => { if (event.data.type === 'CACHE_UPDATE') { const {updatedURL} = event.data.payload; console.log(`${updatedURL} 的更新版本可用`); } }); // 新增事件偵聽器後註冊 service worker。 wb.register(); 複製程式碼
向 serviceWorker 傳送要快取的URL列表
對於某些應用程式,可以知道在構建時需要預先快取的所有資源,但某些應用程式根據使用者首先登陸的 URL 提供完全不同的頁面。
對於後一類別的應用程式,僅快取使用者所訪問的特定頁面所需的資源可能是有意義的。 使用 workbox-routing 軟體包時,您可以向路由器傳送一個 URL 列表進行快取,它將根據路由器本身定義的規則快取這些 URL。
每當啟用新的 serviceWorker 時,此示例都會將頁面載入的 URL 列表傳送到路由器。 請注意,傳送所有 URL 是可以的,因為只會快取與 serviceWorker 中定義的路由匹配的 URL:
const wb = new Workbox('/sw.js'); wb.addEventListener('activated', (event) => { // 獲取當前頁面URL +頁面載入的所有資源。 const urlsToCache = [ location.href, ...performance.getEntriesByType('resource').map((r) => r.name), ]; // 將該URL列表傳送到 serviceWorker 的路由器。 wb.messageSW({ type: 'CACHE_URLS', payload: {urlsToCache}, }); }); // 新增事件偵聽器後註冊 serviceWorker。 wb.register(); 複製程式碼
注意:上述技術適用於通過預設路由器上的 workbox.routing.registerRoute() 方法定義的任何路由。 如果您要建立自己的路由器例項,則需要手動呼叫 addCacheListener() 。
重要的 serviceWorker 生命週期
serviceWorker 的生命週期很複雜,完全可以理解。 它之所以如此複雜,部分原因在於它必須處理 serviceWorker 所有可能使用的所有邊緣情況(例如,註冊多個 serviceWorker,在不同的框架中註冊不同的 serviceWorker,註冊具有不同名稱的 serviceWorker 等)。
但是大多數實現 serviceWorker 的開發人員不應該擔心所有這些邊緣情況,因為它們的使用非常簡單。 大多數開發人員每頁載入只註冊一個 serviceWorker,並且他們不會更改他們部署到伺服器的 serviceWorker 檔案的名稱。
Workbox 類通過將所有 serviceWorker 註冊分為兩類來包含 serviceWorker 生命週期的這個更簡單的檢視:例項自己的註冊 serviceWorker 和外部 serviceWorker:
- 註冊 serviceWorker :由於 Workbox 例項呼叫 register() 而已開始安裝的 serviceWorker,或者如果呼叫 register() 未在註冊時觸發 updatefound 事件,則已啟用安裝 serviceWorker。
- 外部 serviceWorker :一個 serviceWorker,開始獨立於 Workbox 例項呼叫 register() 安裝。 當用戶在另一個標籤頁中開啟新版本的網站時,通常會發生這種情況。
我們的想法是,來自 serviceWorker 的所有生命週期事件都是你的程式碼應該期待的事件,而來自外部 serviceWorker 的所有生命週期事件都應該被視為具有潛在危險,並且應該相應地警告使用者。
考慮到這兩類 serviceWorker,下面是所有重要serviceWorker 生命週期時刻的細分,以及開發人員如何處理它們的建議:
第一次安裝 serviceWorker
你可能希望在 serviceWorker 第一次安裝時不同於處理所有未來更新的方式。
在 Workbox 中,你可以通過檢查以下任何事件的 isUpdate 屬性來區分版本首次安裝和未來更新。 對於第一次安裝,isUpdate 將為 false。
const wb = new Workbox('/sw.js'); wb.addEventListener('installed', (event) => { if (!event.isUpdate) { // 在這裡編寫第一次安裝需要的程式碼 } }); wb.register(); 複製程式碼
時刻 | 事件 | 建議操作 |
---|---|---|
新的 serviceWorker 已安裝(第一次) | installed | serviceWorker 第一次安裝時,通常會預先快取網站離線工作所需的所有資源。 你可以考慮通知使用者他們的站點現在可以離線執行。 此外,由於 serviceWorker 第一次安裝它時不會截獲該頁面載入的獲取事件,你也可以考慮快取已載入的資源(儘管如果這些資源已經被預先快取,則不需要這樣做)。 向上面的快取示例傳送 serviceWorker 的URL列表顯示瞭如何執行此操作。 |
serviceWorker 已經控制頁面 | controlling | 安裝新 serviceWorker 程式並開始控制頁面後,所有後續獲取事件都將通過該 serviceWorker 程式。 如果你的 serviceWorker 添加了任何特殊邏輯來處理特定的 fetch 事件,那麼當你知道邏輯將執行時就是這一點。 請注意,第一次安裝 serviceWorker 時,它不會開始控制當前頁面,除非該 serviceWorker 在其 activate 事件中呼叫 clients.claim()。 預設行為是等到下一頁載入開始控制。 從 workbox-window 的角度來看,這意味著僅在 serviceWorker 呼叫 clients.claim() 的情況下才排程 controlling 事件。 如果在註冊之前已經控制了頁面,則不會排程此事件。 |
serviceWorker 已經完成啟用 | activated | 如上所述,serviceWorker 第一次完成啟用它可能(或可能不)已經開始控制頁面。 因此,你不應該將 activate 事件視為了解 serviceWorker 何時控制頁面的方式。 但是,如果你在活動事件中(在 serviceWorker )執行邏輯,並且你需要知道該邏輯何時完成,則啟用的事件將讓你知道。 |
發現 serviceWorker 的更新版本時
當新 serviceWorker 開始安裝但現有版本當前正在控制該頁面時,以下所有事件的 isUpdate 屬性都將為 true。
在這種情況下,你的反應通常與第一次安裝不同,因為你必須管理使用者何時以及如何獲得此更新。
時刻 | 事件 | 建議操作 |
---|---|---|
已安裝新 serviceWorker(更新前一個) | installed | 如果這不是第一個 serviceWorker 安裝( event.isUpdate === true ),則表示已找到並安裝了較新版本的 serviceWorker(即,與當前控制頁面的版本不同)。 這通常意味著已將更新版本的站點部署到你的伺服器,並且新資源可能剛剛完成預先快取。 注意:某些開發人員使用已安裝的事件來通知使用者其新版本的站點可用。 但是,根據我是否在安裝 serviceWorker 程式中呼叫 skipWaiting(),安裝的 serviceWorker 可能會立即生效,也可能不會立即生效。 如果你確實呼叫 skipWaiting(),那麼最好在新 serviceWorker 啟用後通知使用者更新,如果你沒有呼叫 skipWaiting,最好通知他們等待事件中的掛起更新(見下文了解更多資訊) 細節)。 |
serviceWorker 已安裝,但它仍處於等待階段 | waiting | 如果 serviceWorker 的更新版本在安裝時未呼叫skipWaiting(),則在當前活動 serviceWorker 控制的所有頁面都已解除安裝之前,它不會啟用。 你可能希望通知使用者更新可用,並將在下次訪問時應用。 警告! 開發人員通常會提示使用者重新載入以獲取更新,但在許多情況下重新整理頁面不會啟用已安裝的工作程式。 如果使用者重新整理頁面並且serviceWorker 仍在等待,則等待事件將再次觸發,並且 event.wasWaitingBeforeRegister 屬性將為 true。 請注意,我們計劃在將來的版本中改進此體驗。 關注 問題#1848 以獲取更新。 另一種選擇是提示使用者並詢問他們是否想要獲得更新或繼續等待。 如果選擇獲取更新,則可以使用 postMessage() 告訴 serviceWorker 執行 skipWaiting()。 有關示例,請參閱高階配方為使用者提供頁面重新載入。 |
serviceWorker 已開始控制頁面 | controlling | 當更新的 serviceWorker 開始控制頁面時,這意味著當前控制的 serviceWorker 的版本與載入頁面時控制的版本不同。 在某些情況下可能沒問題,但也可能意味著當前頁面引用的某些資源不再位於快取中(也可能不在伺服器上)。 你可能需要考慮通知使用者頁面的某些部分可能無法正常工作。 注意:如果不在serviceWorker 中呼叫 skipWaiting(),則不會觸發控制事件。 |
serviceWorker 已完成啟用 | activated | 當更新的 serviceWorker 完成啟用時,這意味著你在 serviceWorker 的啟用中執行的任何邏輯都已完成。 如果有什麼需要延遲,直到邏輯完成,這是執行它的時間。 |
找到意外版本的 serviceWorker
有時使用者會在很長一段時間內在後臺標籤中開啟你的網站。 他們甚至可能會開啟一個新標籤並導航到你的網站,卻沒有意識到他們已經在後臺標籤中打開了您的網站。 在這種情況下,您的網站可能同時執行兩個版本,這可能會為開發人員帶來一些有趣的問題。
考慮這樣一種情況,即您的網站的標籤 A 正在執行 v1,標籤 B 正在執行 v2。 載入選項卡 B 時,它將由 v1 附帶的 serviceWorker 版本控制,但伺服器返回的頁面(如果使用網路優先快取策略用於導航請求)將包含所有 v2 資源。
這對於選項卡 B 來說通常不是問題,因為當你編寫 v2 程式碼時,你知道你的 v1 程式碼是如何工作的。但是, 它可能是標籤A的問題 ,因為你的 v1 程式碼無法預測你的 v2 程式碼可能會引入哪些更改。
為了幫助處理這些情況,workbox-window 還會在檢測到來自“外部” serviceWorker 的更新時排程生命週期事件,其中 external 表示任何不是當前 Workbox 例項註冊的版本。
時刻 | 事件 | 建議操作 |
---|---|---|
已安裝外部 serviceWorker | externalinstalled | 如果已安裝外部 serviceWorker,則可能意味著使用者在不同的選項卡中執行你網站的較新版本。 如何響應可能取決於已安裝的服務是進入等待還是活動階段。 |
通過等待啟用來安裝外部 serviceWorker | externalwaiting | 如果外部 serviceWorker 正在等待啟用,則可能意味著使用者試圖在另一個選項卡中獲取你網站的新版本,但是由於此選項卡仍處於開啟狀態,因此他們已被阻止。 如果發生這種情況,你可以考慮向用戶顯示通知,要求他們關閉此標籤。 在極端情況下,你甚至可以考慮呼叫 window.reload(),如果這樣做不會導致使用者丟失任何已儲存的狀態。 |
serviceWorker 外部 serviceWorker 已啟用 | externalactivated | 如果外部 serviceWorker 程式已啟用,則當前頁面很可能無法繼續正常執行。 你可能需要考慮向用戶顯示他們正在執行舊版本頁面的通知,並且可能會出現問題。 |
避免常見錯誤
Workbox 提供的最有用的功能之一是它的開發人員日誌記錄。 對於 worbox-window 也是這樣。
我們知道與 serviceWorker 一起開發往往會讓人感到困惑,當事情發生與你期望的相反時,很難知道原因。
例如,當你對 serviceWorker 進行更改並重新載入頁面時,你可能無法在瀏覽器中看到該更改。 最可能的原因是,你的 serviceWorker 仍在等待啟用。
但是當使用 Workbox 類註冊 serviceWorker 時,你將被告知開發人員控制檯中的所有生命週期狀態更改,這應該有助於除錯為什麼事情不像你期望的那樣。

此外,開發人員在首次使用 serviceWorker 時常犯的錯誤是在錯誤的範圍內註冊 serviceWorker。
為了防止這種情況發生,Workbox類將警告您註冊服務工作者的頁面是否不在該服務工作者的範圍內。 如果您的服務工作者處於活動狀態但尚未控制該頁面,它還會警告你:

window 到 serviceWorker 的溝通
大多數高階 serviceWorker 使用涉及 serviceWorker 和 window 之間的訊息傳遞丟失。 Workbox 類通過提供 messageSW() 方法來幫助解決這個問題,該方法將postMessage() 例項的註冊 serviceWorker 並等待響應。
雖然你可以以任何格式向 serviceWorker 傳送資料,但所有 Workbox 包共享的格式是具有三個屬性的物件(後兩個是可選的):
屬性 | 必須 | 型別 | 描述 |
---|---|---|---|
type | 是 | string | 標識此訊息的唯一字串。 按照慣例,型別都是大寫的,下劃線分隔單詞。 如果型別表示要採取的動作,則它應該是現在時的命令(例如 CACHE_URLS ),如果型別表示報告的資訊,則它應該是過去時(例如 URLS_CACHED )。 |
meta | 否 | string | 在 Workbox 中,這始終是傳送訊息的 Workbox 包的名稱。 自己傳送郵件時,可以省略此屬性或將其設定為你喜歡的任何內容。 |
payload | 否 | * | 正在傳送的資料。 通常這是一個物件,但它不一定是。 |
通過 messageSW() 方法傳送的訊息使用 MessageChannel,因此接收方可以響應它們。 要響應訊息,你可以在訊息事件偵聽器中呼叫 event.ports[0].postMessage(response)。 messageSW() 方法返回一個 promise,該 promise 將解析為你返回的任何響應。
這是一個從 window 到 serviceWorker 傳送訊息並獲得響應的示例。 第一個程式碼塊是 serviceWorker 中的訊息偵聽器,第二個塊使用 Workbox 類傳送訊息並等待響應:
sw.js 中的程式碼:
const SW_VERSION = '1.0.0'; addEventListener('message', (event) => { if (event.data.type === 'GET_VERSION') { event.ports[0].postMessage(SW_VERSION); } }); 複製程式碼
main.js 中的程式碼(執行在 window 環境):
const wb = new Workbox('/sw.js'); wb.register(); const swVersion = await wb.messageSW({type: 'GET_VERSION'}); console.log('Service Worker version:', swVersion); 複製程式碼
管理版本不相容性
上面的示例顯示瞭如何從 window 中實現檢查 serviceWorker 版本。 使用此示例是因為當你在 window 和 serviceWorker 之間來回傳送訊息時,請務必注意你的 serviceWorker 可能沒有執行與你的頁面程式碼執行相同的站點版本,以及 處理此問題的解決方案會有所不同,具體取決於你是以網路優先服務還是快取優先服務。
網路優先
首先為你的網頁提供服務時,你的使用者將始終從你的伺服器獲取最新版本的 HTML。 但是,當用戶第一次重新訪問你的站點時(在部署更新之後),他們獲得的 HTML 將是最新版本,但在其瀏覽器中執行的 serviceWorker 將是先前安裝的版本(可能是許多舊版本)。
理解這種可能性非常重要,因為如果當前版本的頁面載入的 JavaScript 向舊版本的 serviceWorker 傳送訊息,則該版本可能不知道如何響應(或者它可能以不相容的格式響應)。
因此,在進行任何關鍵工作之前,始終對 serviceWorker 進行版本控制並檢查相容版本是個好主意。
例如,在上面的程式碼中,如果該 messageSW() 呼叫返回的 serviceWorker 版本早於預期版本,則最好等到找到更新(這應該在呼叫 register() 時發生)。 此時,你可以通知使用者或更新,也可以手動跳過等待階段以立即啟用新的 serviceWorker。
快取優先
與在網路服務頁面時相比,首先,當你首先提供頁面快取時,你知道你的頁面最初將始終與 serviceWorker 的版本相同(因為這是服務它的原因)。 因此,立即使用messageSW() 是安全的。
但是,如果找到 serviceWorker 的更新版本並在頁面呼叫 register() 時啟用(即你有意跳過等待階段),則向其傳送訊息可能不再安全。
管理這種可能性的一種策略是使用版本控制方案,允許你區分中斷更新和非中斷更新,並且在更新中斷的情況下,你知道向 serviceWorker 傳送訊息是不安全的。 相反,你需要警告使用者他們正在執行舊版本的頁面,並建議他們重新載入以獲取更新。
部落格名稱:王樂平部落格
CSDN部落格地址: blog.csdn.net/lecepin
