在 Google 建構 PWA - 第 1 部分

公布欄團隊在開發 PWA 時,瞭解到的服務工作處理程序。

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

這是關於 Google 公布欄團隊所學到教訓的第一篇網誌文章 同時建構導入外部的 PWA這些文章將分享我們面臨的一些挑戰 我們採取了哪些做法解決這些問題,並提供避免問題的一般建議。以上是 代表 PWA 的完整簡介目的是分享我們團隊親身體驗的經驗。

本文首先將介紹一些背景資訊,然後再深入探討 我們學到了服務工作人員的知識

背景

公布欄 2017 年中至 2019 年中旬。

我們為何選擇建構 PWA

在深入說明開發程序前,讓我們先來探索為何建構 PWA 如此吸引人 選項:

  • 快速疊代的能力:公布欄將在 多個市場。
  • 單一程式碼集:我們的使用者近乎平均分配 Android 和 iOS 版本PWA 就能建構適用於兩個平台的單一網頁應用程式。這會加快執行速度 以及團隊的影響力
  • 快速更新,不受使用者行為影響。PWA 可以自動更新 降低過時用戶端的數量。我們得以推送中斷的後端 大幅縮短客戶的遷移時間
  • 輕鬆整合第一方和第三方應用程式。使用這類整合功能是必要條件 。在 PWA 中,通常只需開啟網址即可。
  • 讓使用者放心安裝應用程式。

我們的架構

公布欄時,我們使用的是 Polymer,以及所有新式且積極支援的版本。 才能正常運作

Service Worker 瞭解情況

如果沒有服務,就無法安裝 PWA 工作站。Service Worker 具備許多強大功能,例如進階快取策略、離線功能、背景同步處理 雖然服務工作處理程序增加了一些複雜性,但我們發現他們帶來的利益遠大於 變得複雜

如果可以,請產生金鑰

避免手動編寫 Service Worker 指令碼。手動撰寫服務工作人員需要手動撰寫 管理快取資源及重寫多數服務工作站程式庫通用的邏輯,例如 Workbox

話雖如此,我們內部技術堆疊不足,無法利用程式庫產生及管理 服務工作處理程序我們有時會根據以下經驗教訓。前往 Pitfalls for 未產生的 Service Worker

並非所有程式庫都與 Service Worker 相容

某些 JS 程式庫假設在 Service Worker 執行時,有不如預期的運作情況。適用對象 執行個體,假設 windowdocument 可用,或使用的 API 無法使用 工作站 (XMLHttpRequest、本機儲存空間等)。確認所需重要程式庫 應用程式與服務工作站相容我們想要在這個特定 PWA 中使用 gapi.js 用於驗證,但之前為 但無法支援 Service Worker。圖書館作者也應減少或移除內容 對 JavaScript 環境做出非必要的假設,以支援 Service Worker 的使用 例如,避免使用與 Service Worker 不相容的 API,以及避免全域 狀態

避免在初始化期間存取 IndexedDB

在以下情況下,不要讀取 IndexedDB 初始化 Service Worker 指令碼,或者遇到以下非預期的情況:

  1. 使用者的網頁應用程式採用 IndexedDB (IDB) 版本 N
  2. 已推送新的網頁應用程式,IDB 版本 N+1
  3. 使用者造訪 PWA,觸發新服務工作處理程序的下載作業
  4. 新的 Service Worker 會先從 IDB 讀取資料,再註冊 install 事件處理常式,觸發 IDB 升級週期從 N 到 N+1
  5. 由於使用者使用的是版本 N 的舊用戶端,因此 Service Worker 升級程序會停止運作 舊版資料庫仍開放連線
  6. Service Worker 停止運作且從未安裝

在我們的案例中,安裝 Service Worker 時快取已失效,所以如果 Service Worker 從未 表示使用者從未收到更新版應用程式。

提高安全性

雖然 Service Worker 指令碼會在背景執行,但使用者也可能隨時終止這些指令碼,即使 在 I/O 作業中 (網路、IDB 等) 執行期間。任何長時間執行的程序 支援續傳功能

如果是同步程序,將大型檔案上傳至伺服器並儲存至 IDB,我們的解決方案 ,利用我們內部的上傳庫的可續傳功能,來中斷部分上傳作業 系統將在上傳前將支援續傳的上傳網址儲存到 IDB,然後使用該網址恢復 才上傳此外,在任何長時間執行的 I/O 作業前 狀態會儲存到 IDB,表明我們處理每筆記錄時所處的階段。

不要依附全域狀態

由於 Service Worker 存在於不同的環境中,因此您可能會預期有許多符號 。許多程式碼都同時在 window 內容和 Service Worker 環境中執行 (例如 例如記錄、旗標、同步等)。程式碼必須能防禦自己使用的服務,例如 本機儲存空間或 Cookie別擔心!您可以使用 globalThis敬上 以在所有情境中運作的方式參照全域物件一併使用儲存的資料 ,因為無法保證指令碼何時終止, 已撤銷的狀態

本機開發

Service Worker 的主要元件是在本機快取資源。不過,在開發階段 是您要的差異,尤其是延遲更新完成時。您仍想要 以便對這個程式問題進行偵錯,或與其他 API 搭配使用,例如: 背景同步處理或通知在 Chrome 中,您可以使用 Chrome 開發人員工具達成以下目的: 在以下位置勾選「Bypass for network」核取方塊 (Application 面板 >「Service worker」窗格), 此外,請在「網路」面板中勾選 [停用快取] 核取方塊,以便 停用記憶體快取。為了涵蓋更多瀏覽器,我們選擇了其他解決方案: 加入旗標,用於停用 Service Worker 中的快取功能 (開發人員預設已啟用) 建構應用程式這可確保開發人員隨時取得最新的變更,而且不會發生任何快取問題。是 請務必加入 Cache-Control: no-cache 標頭,避免瀏覽器 快取任何資產

燈塔

Lighthouse 提供多種偵錯方式 這些實用工具,它會掃描網站並產生報表,包括 PWA、效能 無障礙功能、搜尋引擎最佳化和其他最佳做法 我們建議持續執行 Lighthouse 整合作業。 只要符合 PWA 的標準這實際上發生一次,但 Service Worker 並未安裝 我們在推出正式版之前 都沒想到將 Lighthouse 納入 CI 後 我們又以防萬一

持續推送軟體更新

由於 Service Worker 可以自動更新,因此使用者無法限制升級。這個 大幅降低過時用戶端的數量。使用者開啟應用程式時 Service Worker 會慢慢下載新客戶,才會提供舊用戶端。產生 新用戶端下載完成時,系統會提示使用者重新整理頁面來存取新功能。即使 使用者忽略了這項要求,下次重新整理網頁時,就會收到新的 用戶端版本。因此,使用者在相同步驟中 發布在 iOS/Android 應用程式中

我們能在短時間內將破壞後端變更推送出去,而且遷移時間極短, 用戶端。一般來說,我們會預留一個月的時間,讓使用者在升級前更新至新版用戶端 破壞性變更。由於應用程式會在過時的狀態下放送,舊型客戶有可能還是有可能 。在 iOS 上,Service Worker 在幾週後撤銷 所以不會發生這種情形對 Android 系統來說,如果系統在遊戲期間未提供服務,就可解決這個問題 。實務上,我們從 問題。特定團隊希望投入的程度取決於具體用途 但 PWA 比 iOS/Android 應用程式更靈活。

在 Service Worker 中取得 Cookie 值

有時需要存取 Service Worker 環境中的 Cookie 值。在這個範例中 需要存取 Cookie 值來產生權杖,以便驗證第一方 API 要求。在 Service Worker 無法使用同步 API,例如 document.cookies。您可以隨時傳送 訊息傳送給有效 (視窗) 用戶端,以便要求 Cookie 值,不過 Service Worker 可以在沒有視窗用戶端的情況下,在背景執行 就像在背景同步處理時一樣為解決這個問題,我們在 直接將 Cookie 值傳回用戶端的前端伺服器。Service Worker 會將 並讀取回應以取得 Cookie 值。

自 2023 年 4 月發布以來 Cookie Store API, 除非瀏覽器提供上述解決方法,因此不再需要使用 非同步存取瀏覽器 Cookie,並可直接由 Service Worker 使用。

非產生的服務工作站陷阱

確保如有靜態快取檔案變更時,Service Worker 指令碼會隨之變更

常見的 PWA 模式,是讓 Service Worker 在 install 階段,可讓用戶端直接針對 後續造訪 。只有在瀏覽器偵測到 Service 時,系統才會安裝 Service Worker 工作站指令碼在某些地方有所變更,因此我們必須確保 Service Worker 指令碼檔案本身 快取檔案有所變更。如要手動執行這項作業,請將 建立在服務工作處理程序指令碼中的靜態資源檔案集,因此每個版本都會產生專屬的 Service Worker JavaScript 檔案。Service Worker 程式庫,例如 Workbox 會自動為您執行這項程序。

單元測試

將事件監聽器新增至全域物件,藉此執行 Service Worker API 函式。例如:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

這在測試時可能會很困難,因為您必須模擬事件觸發條件、事件物件, respondWith() 回呼,然後等待此承諾,最後才對結果做出斷言。一個 可以簡化此架構的結構,就是將所有實作項目委派給其他檔案,但這樣比較簡單 目標使用者、使用的資料 以及模型訓練與測試方式

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

由於針對 Service Worker 指令碼進行單元測試很困難,我們保留了核心服務工作人員 盡量簡化指令碼,將大部分實作項目分割成其他模組。開始時間 只是標準 JS 模組,使用標準測試可更輕鬆地進行單元測試 程式庫

請密切關注第 2 和第 3 部分

本系列的第 2 和第 3 部分將探討媒體管理和 iOS 特有的問題。如果發生以下情況: 想進一步瞭解如何於 Google 建構 PWA,請前往我們的作者個人資料查詢 如何與我們聯絡: