啟用 Service Worker 支援不只是註冊 Service Worker 而已,它還提供多個服務,讓你與 Service Worker 互動,並控制應用程式的快取行為。
SwUpdate 服務
SwUpdate 服務讓你存取多個事件,這些事件會指出 Service Worker 何時發現並安裝應用程式可用的更新。
SwUpdate 服務支援三種獨立的操作:
- 在伺服器上_偵測_到新版本、_已安裝並準備好_可在本機使用,或_安裝失敗_時,接收通知。
- 要求 Service Worker 向伺服器檢查新更新。
- 要求 Service Worker 為目前的分頁啟用最新版本的應用程式。
版本更新
versionUpdates 是 SwUpdate 的 Observable 屬性,會發送四種事件類型:
| 事件類型 | 詳細說明 |
|---|---|
VersionDetectedEvent |
當 Service Worker 在伺服器上偵測到應用程式的新版本,並即將開始下載時發送。 |
NoNewVersionDetectedEvent |
當 Service Worker 已檢查伺服器上的應用程式版本,且沒有找到新版本時發送。 |
VersionReadyEvent |
當應用程式的新版本可供用戶端啟用時發送。可用來通知使用者有可用更新,或提示他們重新整理頁面。 |
VersionInstallationFailedEvent |
當新版本安裝失敗時發送。可用於記錄/監控用途。 |
log-update.service.ts
@Service()
export class LogUpdateService {
private updates = inject(SwUpdate);
constructor() {
this.updates.versionUpdates.subscribe((evt) => {
switch (evt.type) {
case 'VERSION_DETECTED':
console.log(`Downloading new app version: ${evt.version.hash}`);
break;
case 'VERSION_READY':
console.log(`Current app version: ${evt.currentVersion.hash}`);
console.log(`New app version ready for use: ${evt.latestVersion.hash}`);
break;
case 'VERSION_INSTALLATION_FAILED':
console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`);
break;
}
});
}
}
檢查更新
你可以要求 Service Worker 檢查伺服器上是否已部署任何更新。 Service Worker 會在初始化期間以及每次導覽請求時檢查更新——也就是使用者從不同位址導覽到你的應用程式時。 不過,如果你的網站經常變更,或想讓更新依排程發生,你也可以選擇手動檢查更新。
使用 checkForUpdate() 方法來完成:
check-for-update.service.ts
import {ApplicationRef, inject, Service} from '@angular/core';
import {SwUpdate} from '@angular/service-worker';
import {concat, interval, first} from 'rxjs';
@Service()
export class CheckForUpdateService {
private appRef = inject(ApplicationRef);
private updates = inject(SwUpdate);
constructor() {
// Allow the app to stabilize first, before starting
// polling for updates with `interval()`.
const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
everySixHoursOnceAppIsStable$.subscribe(async () => {
try {
const updateFound = await this.updates.checkForUpdate();
console.log(updateFound ? 'A new version is available.' : 'Already on the latest version.');
} catch (err) {
console.error('Failed to check for updates:', err);
}
});
}
}
這個方法會回傳一個 Promise<boolean>,指出是否有可供啟用的更新。
檢查可能會失敗,這會導致 Promise 被拒絕。
穩定狀態與 Service Worker 註冊
為了避免對頁面的初始渲染造成負面影響,Angular Service Worker 服務預設會等待應用程式穩定,最多等待 30 秒,之後才註冊 ServiceWorker 指令碼。
持續輪詢更新,例如使用 setInterval() 或 RxJS 的 interval(),會讓應用程式無法穩定,ServiceWorker 指令碼也就不會註冊到瀏覽器,直到達到 30 秒的上限為止。
你的應用程式所做的任何輪詢都是如此。 更多資訊,請參閱 isStable 文件。
若要避免這段延遲,請如前例所示,先等待應用程式穩定,再開始輪詢更新。 或者,你也可以為 ServiceWorker 定義不同的註冊策略。
更新到最新版本
你可以在新版本準備好時,立即重新載入頁面,把既有分頁更新到最新版本。 為了避免打斷使用者的進度,一般來說,最好先提示使用者,讓他們確認可以重新載入頁面並更新到最新版本:
prompt-update.service.ts
@Service()
export class PromptUpdateService {
constructor() {
const swUpdate = inject(SwUpdate);
swUpdate.versionUpdates
.pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'))
.subscribe((evt) => {
if (promptUser(evt)) {
// Reload the page to update to the latest version.
document.location.reload();
}
});
}
}
不重新載入就更新的安全性
呼叫 activateUpdate() 可以不重新載入頁面就把分頁更新到最新版本,但這可能會讓應用程式損壞。
不重新載入就更新,可能會造成應用程式殼層與其他頁面資源之間的版本不一致,例如延遲載入的 chunk,它們的檔案名稱可能會在版本之間變更。
你只應該在確定對你的特定使用情境是安全的情況下,才使用 activateUpdate()。
處理無法復原的狀態
在某些情況下,Service Worker 用來服務用戶端的應用程式版本,可能處於損壞狀態,若不完整重新載入頁面就無法復原。
例如,想像以下情境:
使用者第一次開啟應用程式,Service Worker 快取了最新版本的應用程式。 假設應用程式已快取的資產包括
index.html、main.<main-hash-1>.js與lazy-chunk.<lazy-hash-1>.js。使用者關閉應用程式,一段時間沒有再開啟。
過了一段時間後,新版本的應用程式部署到伺服器。 這個較新的版本包括
index.html、main.<main-hash-2>.js與lazy-chunk.<lazy-hash-2>.js等檔案。
IMPORTANT: 現在雜湊值不同了,因為檔案內容已變更。舊版本在伺服器上已不再可用。
同時,使用者的瀏覽器決定把
lazy-chunk.<lazy-hash-1>.js從快取中清除。 瀏覽器可能會為了回收磁碟空間,決定清除快取中的特定(或全部)資源。使用者再次開啟應用程式。 Service Worker 提供它此時所知的最新版本,也就是舊版本(
index.html與main.<main-hash-1>.js)。在稍後的某個時間點,應用程式請求延遲載入的 bundle:
lazy-chunk.<lazy-hash-1>.js。Service Worker 在快取中找不到該資產(記得它已被瀏覽器清除)。 它也無法從伺服器取得(因為伺服器現在只有較新版本的
lazy-chunk.<lazy-hash-2>.js)。
在上述情境中,Service Worker 無法提供一個通常會被快取的資產。
該應用程式版本已損壞,若不重新載入頁面,就無法修復用戶端的狀態。
在這種情況下,Service Worker 會發送一個 UnrecoverableStateEvent 事件來通知用戶端。
訂閱 SwUpdate#unrecoverable 即可收到通知並處理這些錯誤。
handle-unrecoverable-state.service.ts
@Service()
export class HandleUnrecoverableStateService {
private updates = inject(SwUpdate);
constructor() {
this.updates.unrecoverable.subscribe((event) => {
notifyUser(
'An error occurred that we cannot recover from:\n' +
event.reason +
'\n\nPlease reload the page.',
);
});
}
}
更多 Angular Service Worker 資訊
你可能也會對以下內容有興趣: