擴充生態系
Service Worker 與 PWA

開始使用 service worker

本文件說明如何在使用 Angular CLI 建立的專案中啟用 Angular service worker 支援,接著透過範例實際示範 service worker 的運作,展示載入與基本快取的行為。

將 service worker 加入你的專案

若要在專案中設定 Angular service worker,請執行下列 CLI 指令:

ng add @angular/pwa

CLI 會透過下列動作,將你的應用程式設定為使用 service worker:

  1. 將 @angular/service-worker 套件加入你的專案。
  2. 在 CLI 中啟用 service worker 建置支援。
  3. 匯入 service worker,並向應用程式的根 providers 註冊。
  4. 更新 index.html 檔案:
    • 加入連結,引入 manifest.webmanifest 檔案
    • 加入 theme-color 的 meta 標籤
  5. 安裝圖示檔案,以支援安裝後的 Progressive Web App(PWA)。
  6. 建立名為 ngsw-config.json 的 service worker 設定檔, 用來指定快取行為與其他設定。

接著,建置專案:

ng build

CLI 專案現在已設定完成,可以使用 Angular service worker。

Service worker 實際運作:導覽

本節使用範例應用程式,實際示範 service worker 的運作。 若要在本機開發期間啟用 service worker 支援,請使用正式環境設定,執行下列指令:

ng serve --configuration=production

或者,你也可以使用 npm 上的 http-server package, 它支援 service worker 應用程式。執行下列指令即可在不安裝的情況下使用:

npx http-server -p 8080 -c-1 dist/<project-name>/browser

這樣就能在 http://localhost:8080 以支援 service worker 的方式提供你的應用程式。

首次載入

讓伺服器在連接埠 8080 上執行後,將瀏覽器指向 http://localhost:8080。 你的應用程式應該會正常載入。

TIP: 測試 Angular service worker 時,建議使用瀏覽器的無痕或私密視窗,確保 service worker 不會讀到先前殘留的狀態,以免造成非預期的行為。

HELPFUL: 若你不是使用 HTTPS,service worker 只會在存取 localhost 上的應用程式時才註冊。

模擬網路問題

若要模擬網路問題,請停用應用程式的網路互動。

在 Chrome 中:

  1. 選取 Tools > Developer Tools(從 Chrome 右上角的選單進入)。
  2. 前往 Network tab。
  3. 在 Throttling 下拉選單中選取 Offline。
Network 分頁中的離線選項已被選取

現在應用程式已無法進行網路互動。

對於未使用 Angular service worker 的應用程式,此時重新整理會顯示 Chrome 的網路中斷頁面,上面寫著「There is no Internet connection」。

加入 Angular service worker 後,應用程式的行為就不同了。 重新整理時,頁面會正常載入。

查看 Network 分頁,確認 service worker 已在運作。

請求被標示為來自 ServiceWorker

HELPFUL: 在「Size」欄位下,請求的狀態是 (ServiceWorker)。 這表示資源不是從網路載入的, 而是從 service worker 的快取載入。

哪些內容被快取了?

請注意,瀏覽器呈現這個應用程式所需的全部檔案都已被快取。 ngsw-config.json 的範本設定會快取 CLI 所使用的特定資源:

  • index.html(以及採用伺服器端渲染或預渲染的應用程式所用的 index.csr.html)
  • favicon.ico 與 manifest.webmanifest
  • 建置產物(JS 與 CSS 套件)
  • 瀏覽器輸出中各處的圖片與字型(預設為 ./dist/<project-name>/browser/)

IMPORTANT: 產生的 ngsw-config.json 只包含有限的可快取字型與圖片副檔名清單。在某些情況下,你可能需要修改 glob 模式以符合你的需求。

修改你的應用程式

現在你已經了解 service worker 如何快取你的應用程式,下一步是了解更新如何運作。 對應用程式做一項變更,並觀察 service worker 如何安裝更新:

  1. 如果你正在無痕視窗中測試,請再開一個空白分頁。 這樣能在測試期間保持無痕模式與快取狀態存活。

  2. 關閉應用程式分頁,但不要關閉視窗。 這也會一併關閉 Developer Tools。

  3. 關閉 http-server(Ctrl-c)。

  4. 開啟 src/app/app.html 進行編輯。

  5. 將文字 Hello, {{ title() }} 改為 Bienvenue à {{ title() }}!。

  6. 再次建置並執行伺服器:

ng build
npx http-server -p 8080 -c-1 dist/<project-name>/browser

在瀏覽器中更新你的應用程式

接著看看瀏覽器與 service worker 如何處理更新後的應用程式。

  1. 在同一個視窗中再次開啟 http://localhost:8080。 發生了什麼事?

    它仍顯示 Welcome to Service Workers!

    哪裡出錯了? 其實什麼都沒出錯! Angular service worker 正在做它的工作,提供它已安裝的應用程式版本,即使已有更新可用。 為了速度,service worker 在提供已快取的應用程式之前,不會先等待檢查更新。

    查看 http-server 的記錄,可以看到 service worker 正在請求 /ngsw.json。

    [2023-09-07T00:37:24.372Z]  "GET /ngsw.json?ngsw-cache-bust=0.9365263935102124" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"

    這就是 service worker 檢查更新的方式。

  2. 重新整理頁面。

    文字已改為 Bienvenue à app!

    service worker 已在_背景_安裝更新後的應用程式版本,下次載入或重新載入頁面時,service worker 就會切換到最新版本。

Service worker 設定

Angular service worker 透過 SwRegistrationOptions 介面支援完整的設定選項,讓你能精細控制註冊行為、快取與指令碼執行。

啟用與停用 service worker

enabled 選項控制是否註冊 service worker,以及相關服務是否嘗試與它通訊。

import {ApplicationConfig, isDevMode} from '@angular/core';
import {provideServiceWorker} from '@angular/service-worker';

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(), // Disable in development, enable in production
    }),
  ],
};

使用 updateViaCache 控制快取

updateViaCache 選項控制瀏覽器在 service worker 更新期間如何查閱 HTTP 快取。這讓你能精細控制瀏覽器何時擷取更新後的 service worker 指令碼與匯入的模組。

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      updateViaCache: 'imports',
    }),
  ],
};

updateViaCache 選項接受下列值:

  • 'imports':查閱 HTTP 快取以取得 service worker 指令碼所匯入的指令碼,但不查閱 service worker 指令碼本身
  • 'all':同時查閱 HTTP 快取以取得 service worker 指令碼與其匯入的指令碼
  • 'none':不查閱 HTTP 快取以取得 service worker 指令碼或其匯入的指令碼

使用 type 選項支援 ES Module

type 選項讓你在註冊 service worker 時指定指令碼類型,讓你的 service worker 指令碼能支援 ES module 功能。

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      type: 'module', // Enable ES module features
    }),
  ],
};

type 選項接受下列值:

  • 'classic'(預設):傳統的 service worker 指令碼執行方式。指令碼中不允許使用 import 與 export 等 ES module 功能
  • 'module':將指令碼註冊為 ES module。允許使用 import/export 語法與模組功能

註冊範圍控制

scope 選項定義 service worker 的註冊範圍,決定它能控制的 URL 範圍。

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      scope: '/app/', // Service worker will only control URLs under /app/
    }),
  ],
};
  • 控制 service worker 可以攔截與管理哪些 URL
  • 預設情況下,範圍是包含 service worker 指令碼的目錄
  • 在呼叫 ServiceWorkerContainer.register() 時使用

註冊策略設定

registrationStrategy 選項定義 service worker 何時向瀏覽器註冊,讓你能控制註冊的時機。

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};

可用的註冊策略:

  • 'registerWhenStable:timeout'(預設:'registerWhenStable:30000'):在應用程式穩定後(沒有待處理的 micro-/macro-tasks)就註冊,但不晚於指定的逾時時間(毫秒) 逾時時間是必填的。若未提供,'registerWhenStable' 會立即註冊 service worker。
  • 'registerImmediately':立即註冊 service worker
  • 'registerWithDelay:timeout':延遲指定的逾時時間(毫秒)後再註冊
// Register immediately
export const immediateConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerImmediately',
    }),
  ],
};

// Register with a 5-second delay
export const delayedConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWithDelay:5000',
    }),
  ],
};

你也可以提供 Observable 工廠函式,來自訂註冊時機:

import {ApplicationConfig, isDevMode} from '@angular/core';
import {provideServiceWorker} from '@angular/service-worker';
import {timer} from 'rxjs';

export const customConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: () => timer(10_000), // Register after 10 seconds
    }),
  ],
};

深入了解 Angular service worker

你可能也會對下列內容有興趣: