workbox-window
套件是一組模組,可在 window
結構定義中執行 (也就是在您的網頁中)。這些元件可補充在 Service Worker 中執行的其他 Workbox 套件。
workbox-window
的主要功能/目標為:
- 為了簡化服務工作站註冊與更新的程序,請協助開發人員找出服務工作站生命週期中最關鍵時刻,並更輕鬆地回應這些時刻。
- 為了避免開發人員犯下最常見的錯誤。
- 讓在服務工作站中執行的程式碼與在視窗中執行的程式碼之間能夠更輕鬆通訊。
匯入及使用工作方塊視窗
workbox-window
套件的主要進入點為 Workbox
類別,您可以從我們的 CDN 或使用任何熱門的 JavaScript 組合工具匯入您的程式碼。
使用我們的 CDN
將 Workbox
類別匯入網站最簡單的方法,就是從我們的 CDN 匯入:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
請注意,這個範例使用 <script type="module">
和 import
陳述式載入 Workbox
類別。雖然您可能認為必須轉譯這個程式碼,才能在舊版瀏覽器中運作,但其實並非必要。
支援 Service Worker 的所有主要瀏覽器也支援原生 JavaScript 模組,因此非常適合將這段程式碼提供給任何瀏覽器 (舊版瀏覽器只會忽略這項程式碼)。
使用 JavaScript 套件載入 Workbox
雖然使用 workbox-window
絕對不需要工具,但如果您的開發基礎架構已包含可與 npm 依附元件搭配使用的 webpack 或 Rollup 等整合工具,則可使用這些項目載入 workbox-window
。
第一步是安裝
workbox-window
做為應用程式的依附元件:
npm install workbox-window
接著,在應用程式的其中一個 JavaScript 檔案中參照 workbox-window
套件名稱,以 import
工作箱:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
如果您的套裝組合支援透過動態匯入陳述式進行程式碼分割,您也可以有條件載入 workbox-window
,這有助於縮減頁面主要套件的大小。
即使 workbox-window
很小,也不需要將其與網站的核心應用程式邏輯一起載入,因為服務工作站的本質就是漸進式的改進。
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
進階組合概念
與在 Service Worker 中執行的 Workbox 套件不同,workbox-window
的 main
和 module
欄位參照的建構檔案會轉譯為 ES5。package.json
這使得程式庫與今日的建構工具相容,其中部分工具不允許開發人員轉換任何 node_module
依附元件。
如果您的建構系統「確實」允許您轉譯依附元件 (或者不需要轉譯任何程式碼),建議您匯入特定來源檔案,而不要匯入套件本身。
以下是匯入 Workbox
的各種方式,以及各傳回內容的說明:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
示例
匯入 Workbox
類別後,即可用該類別註冊,並與服務工作站互動。以下列舉一些可以在應用程式中使用 Workbox
的方法:
註冊 Service Worker,並在第一次使用 Service Worker 時通知使用者
許多網頁應用程式使用者服務工作站會預先快取資產,以便在後續載入頁面時離線運作。在某些情況下,建議您告知使用者應用程式現在已可離線使用。
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
如果服務工作處理程序已安裝完畢,但無法順利啟動,請通知使用者
根據預設,當現有服務工作站控制的頁面註冊新的服務工作站時,在初始服務工作站控管的所有用戶端都完全卸載之前,Service Worker 才會啟動。
這可能會讓開發人員感到困惑,尤其是重新載入目前頁面未導致新 Service Worker 啟用的情況下。
為盡量避免混淆,並讓使用者清楚瞭解情況發生,Workbox
類別提供您可以監聽的 waiting
事件:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
通知使用者 workbox-broadcast-update
套件的快取更新
workbox-broadcast-update
套件是從快取提供內容 (快速交付) 的絕佳方式,也能使用過時/重新驗證策略通知使用者更新內容。
如要從視窗接收這些更新,您可以監聽 CACHE_UPDATED
類型的 message
事件:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
將要快取的網址清單傳送給服務工作人員
在某些應用程式中,您可知道在建構期間需要預先快取的所有素材資源,但有些應用程式會根據使用者最初到達的網址,提供完全不同的網頁。
針對後者類別的應用程式,建議您只快取使用者造訪的特定網頁所需的資產。使用 workbox-routing
套件時,您可向路由器傳送要快取的網址清單,系統會根據路由器本身定義的規則快取這些網址。
這個範例會在啟用新的 Service Worker 時,將網頁載入的網址清單傳送至路由器。請注意,您可以傳送「所有」網址,因為系統只會快取與 Service Worker 中已定義路徑相符的網址:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
重要服務工作站生命週期時刻
服務工作站生命週期相當複雜,難以完整理解。這個流程非常複雜,其中一項原因是服務工作站的所有可能使用情況都必須處理所有邊緣案例 (例如註冊多個 Service Worker、在不同影格中註冊不同的 Service Worker、使用不同的名稱註冊 Service Worker 等)。
但大多數實作 Service Worker 的開發人員都不需要擔心所有這些邊緣案例,因為它們的用法相當簡單。大部分的開發人員每次載入網頁時只會註冊一個 Service Worker,且不會變更部署至伺服器的 Service Worker 名稱。
Workbox
類別會將所有服務工作站註冊資料分為兩種類別,分別是執行個體自有、已註冊的 Service Worker 和外部 Service Worker,因此更適合用於服務工作站生命週期的簡易檢視畫面:
- 已註冊的 Service Worker:在註冊時呼叫
register()
時,因Workbox
執行個體呼叫register()
或已經啟用的 Service Worker 而開始安裝的 Service Worker。updatefound
- 外部 Service Worker:獨立開始安裝呼叫
register()
的Workbox
例項的 Service Worker。這種情況,通常是因為使用者在另一個分頁中開啟了網站的新版本。如果事件源自外部 Service Worker,則事件的isExternal
屬性會設為true
。
在瞭解這兩種服務工作處理程序後,以下將詳細介紹所有重要的服務工作處理程序生命週期時刻,以及開發人員建議如何處理這些事件:
第一次安裝 Service Worker
您可能需要將服務工作站首次安裝與日後所有更新的方式不同。
在 workbox-window
中,您可以檢查下列任一事件的 isUpdate
屬性,區分版本首次安裝和日後更新。如果是第一次安裝,isUpdate
會是 false
。
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
找到 Service Worker 的更新版本時
當新的 Service Worker 開始安裝,但現有版本正在控制該頁面時,下列所有事件的 isUpdate
屬性將為 true
。
這種情況與首次安裝時的回應方式通常和首次安裝不同,因為您必須管理使用者取得此更新的時間和方式。
發現未預期的 Service Worker 版本時
有時使用者會在背景分頁中長時間開啟您的網站。他們甚至可能開啟新分頁並瀏覽您的網站,卻不知道他們已在背景分頁中開啟您的網站。在這種情況下,您的網站有兩種版本可能會同時執行,而且開發人員可能會遇到一些有趣的問題。
假設在網站 A 執行 v1 與執行 v2 的分頁 B,載入分頁 B 時,將由隨附 v1 的服務工作站版本控制,但伺服器傳回的頁面 (如果您的導覽要求使用網路優先快取策略) 會包含您所有的 v2 資產。
但分頁 B 通常不會造成問題,因為當您編寫第 2 版程式碼時,已瞭解 v1 程式碼的運作方式。不過,這對分頁 A 可能有問題,因為您的 v1 程式碼無法預測變更 v2 程式碼可能引入的變更。
為協助處理這類情況,workbox-window
也會在偵測到來自「外部」服務工作站的更新時,分派生命週期事件,其中外部僅代表任何版本並非目前註冊的 Workbox
執行個體版本。
自 Workbox 6 以上版本起,這些事件與上述事件相等,並會在每個事件物件上新增 isExternal: true
屬性。如果網頁應用程式需要實作特定邏輯來處理「外部」服務工作站,您可以在事件處理常式中查看該屬性。
避免常見錯誤
Workbox 最實用的功能之一就是開發人員記錄。這對 workbox-window
尤其重要。
我們瞭解使用 Service Worker 進行開發時經常令人感到困惑,如果情況與您預期的不同,我們可能難以理解原因。
例如,當您變更服務工作站並重新載入頁面時,您可能不會在瀏覽器中看到這項變更。最有可能的原因是您的服務工作站仍在等待啟動。
不過,當您使用 Workbox
類別註冊 Service Worker 時,您將會收到開發人員主控台中的所有生命週期狀態變更相關資訊,這應該有助於偵錯問題未如預期的原因。
此外,開發人員第一次使用 Service Worker 時,經常會犯錯的錯誤,就是在錯誤範圍中註冊 Service Worker。
為了避免發生這種情形,如果註冊 Service Worker 的網頁不在該 Service Worker 的範圍內,Workbox
類別會顯示警告。如果服務工作站已啟用,但尚未控制頁面,系統也會發出警告:
Service Worker 通訊視窗
大部分的進階服務工作站用量牽涉到 Service Worker 和視窗之間傳送大量訊息。Workbox
類別也能提供 messageSW()
方法,這會對執行個體已註冊的 Service Worker 執行 postMessage()
並等待回應,以助您一臂之力。
雖然您可以採用任何格式將資料傳送至 Service Worker,但所有 Workbox 套件共用的格式都是包含三個屬性的物件 (後者是選用屬性):
透過 messageSW()
方法傳送的訊息會使用 MessageChannel
,因此接收器才能回應訊息。如要回覆訊息,您可以在訊息事件監聽器中呼叫 event.ports[0].postMessage(response)
。messageSW()
方法會傳回承諾,該承諾會解析為您回覆的任何 response
。
以下示範如何將訊息從視窗傳送至 Service Worker,並取得回應。第一個程式碼區塊是服務工作站中的訊息事件監聽器,第二個區塊則使用 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 中的程式碼 (在視窗中執行):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
管理版本不相容的情況
以上範例示範如何透過視窗檢查 Service Worker 版本。這個範例的用意是,因為當您在視窗與 Service Worker 之間來回傳送訊息時,請務必注意,服務工作站執行的網站版本可能與執行網頁程式碼的版本不同,而解決這個問題的解決方案則因網頁優先服務或快取優先。
先連上網路
先提供網頁網路時,使用者一律會從您的伺服器取得最新版本的 HTML。不過,使用者初次造訪網站 (部署更新後) 時,使用者收到的 HTML 將是最新版本,但在瀏覽器中執行的 Service Worker 會是之前安裝的版本 (可能是許多舊版本)。
請務必瞭解這種可能性,因為如果網頁目前版本載入的 JavaScript 會將訊息傳送至舊版的 Service Worker,可能就無法知道該版本的回應方式,或是可能會以不相容的格式回應。
因此,建議您在執行任何重要工作前,一律為 Service Worker 執行版本版本檢查,並檢查是否有相容版本。
例如,在上述程式碼中,如果 messageSW()
呼叫傳回的 Service Worker 版本比預期版本更舊,建議等到找到更新 (在您呼叫 register()
時會發生) 為止。屆時,您可以通知使用者或更新該階段,也可以手動略過等候階段,立即啟用新的 Service Worker。
先快取
不同於以網路為優先提供網頁快取的情況,在優先提供網頁快取時,您會知道自己的網頁最初永遠會與服務工作站的版本相同 (因為用到的是服務工作站)。因此,您可以放心立即使用 messageSW()
。
不過,如果系統在網頁呼叫 register()
時找到並啟動 Service Worker 的更新版本 (也就是您刻意略過等候階段),則傳送訊息到工作站可能就沒有問題。
管理這種可能性的策略之一,是使用版本管理配置,可區分破壞性更新和非破壞性更新;而在破壞性更新的情況下,您知道傳送訊息給 Service Worker 並不安全。請改為警告使用者正在執行舊版頁面,建議使用者重新載入頁面,取得更新。
略過等待輔助程式
視窗對服務工作站訊息傳遞作業的常見使用慣例是傳送 {type: 'SKIP_WAITING'}
訊息,指示已安裝的服務工作站略過等候階段並啟用。
自 Workbox v6 起,messageSkipWaiting()
方法可用來傳送 {type: 'SKIP_WAITING'}
訊息給與目前 Service Worker 註冊相關聯的等候服務工作站。如果沒有等待 Service Worker,則不會採取任何行動。
類型
Workbox
可協助處理 Service Worker 註冊、更新,以及對服務工作站生命週期事件做出回應的類別。
屬性
-
建構函式
void
建立具有指令碼網址和服務工作站選項的新 Workbox 執行個體。指令碼網址和選項與呼叫 navigator.serviceWorker.register(scriptURL, options) 時使用的指令碼網址和選項相同。
constructor
函式如下所示:(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
-
scriptURL
string | TrustedScriptURL
與這個執行個體相關聯的 Service Worker 指令碼。系統支援使用
TrustedScriptURL
。 -
registerOptions
物件選用
-
returns
-
-
已啟用
Promise<ServiceWorker>
-
控制
Promise<ServiceWorker>
-
getSW
void
可供使用的 Service Worker 參照完成解析,該參照與這個執行個體的指令碼網址相符。
如果在註冊時已有有效或等待中的服務工作站具備相符的指令碼網址,系統就會使用該工作站 (如果兩者相符,則等待服務工作站優先於執行中的服務工作站,因為等待中的服務工作站最近註冊的時間較高)。如果註冊時沒有相符的有效或等待中的 Service Worker,則必須等到找到更新並開始安裝後,才會解析承諾項目,屆時系統會使用安裝的 Service Worker。
getSW
函式如下所示:() => {...}
-
returns
Promise<ServiceWorker>
-
-
messageSW
void
將傳遞的資料物件 (透過
workbox-window.Workbox#getSW
) 傳送至由此執行個體註冊的 Service Worker,並用回應 (如果有的話) 解析。呼叫
event.ports[0].postMessage(...)
即可在服務工作站的訊息處理常式中設定回應,以解決messageSW()
傳回的承諾。如未設定回應,承諾永遠無法解決。messageSW
函式如下所示:(data: object) => {...}
-
資料或曾存取這類資料的人員
物件
要傳送至 Service Worker 的物件
-
returns
承諾<任何>
-
-
messageSkipWaiting
void
傳送
{type: 'SKIP_WAITING'}
訊息給目前處於與目前註冊相關聯的waiting
狀態的 Service Worker。如果目前沒有註冊狀態,或沒有任何 Service Worker 是
waiting
,則呼叫此方法不會有任何作用。messageSkipWaiting
函式如下所示:() => {...}
-
register
void
為這個執行個體指令碼網址和服務工作站選項註冊服務工作站。根據預設,這個方法會延遲註冊,直到視窗載入後才會執行。
register
函式如下所示:(options?: object) => {...}
-
選項
物件選用
-
立即
布林值 (選用)
-
-
returns
Promise<ServiceWorkerRegistration>
-
-
update
void
檢查已註冊的 Service Worker 更新。
update
函式如下所示:() => {...}
-
returns
Promise<void>
-
WorkboxEventMap
屬性
WorkboxLifecycleEvent
屬性
-
isExternal
布林值 (選用)
-
isUpdate
布林值 (選用)
-
originalEvent
活動 選用
-
sw
ServiceWorker (選用)
-
目標
WorkboxEventTarget 選用
-
類型
typeOperator
WorkboxLifecycleEventMap
屬性
WorkboxLifecycleWaitingEvent
屬性
-
isExternal
布林值 (選用)
-
isUpdate
布林值 (選用)
-
originalEvent
活動 選用
-
sw
ServiceWorker (選用)
-
目標
WorkboxEventTarget 選用
-
類型
typeOperator
-
wasWaitingBeforeRegister
布林值 (選用)
WorkboxMessageEvent
屬性
-
資料或曾存取這類資料的人員
不限
-
isExternal
布林值 (選用)
-
originalEvent
活動
-
ports
typeOperator
-
sw
ServiceWorker (選用)
-
目標
WorkboxEventTarget 選用
-
類型
方法
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
透過 postMessage
將資料物件傳送至服務工作站,並透過回應解析 (如果有的話)。
呼叫 event.ports[0].postMessage(...)
即可在服務工作站的訊息處理常式中設定回應,以解決 messageSW()
傳回的承諾。如未設定回應,承諾就無法解決。
參數
-
sw
ServiceWorker
要接收訊息的 Service Worker。
-
資料或曾存取這類資料的人員
物件
要傳送至 Service Worker 的物件。
傳回
-
承諾<任何>