Angular 生產(chǎn)環(huán)境下的Service Worker

2022-07-12 10:21 更新

生產(chǎn)環(huán)境下的 Service Worker

本頁講的是如何使用 Angular Service Worker 發(fā)布和支持生產(chǎn)環(huán)境下的應(yīng)用。它解釋了 Angular Service Worker 如何滿足大規(guī)模生產(chǎn)環(huán)境的需求、Service Worker 在多種條件下有哪些行為以及有哪些可用的資源和故障保護(hù)機(jī)制。

Service Worker 與應(yīng)用資源的緩存

從概念上說,你可以把 Angular Service Worker 想象成一個轉(zhuǎn)發(fā)式緩存或裝在最終用戶瀏覽器中的 CDN 邊緣。Service Worker 的工作是從本地緩存中滿足 Angular 應(yīng)用對資源或數(shù)據(jù)的請求,而不用等待網(wǎng)絡(luò)。和所有緩存一樣,它有一些規(guī)則來決定內(nèi)容該如何過期或更新。

應(yīng)用的版本

在 Angular Service Worker 的語境下,“版本”是指用來表示 Angular 應(yīng)用的某一次構(gòu)建成果的一組資源。當(dāng)應(yīng)用的一個新的構(gòu)建發(fā)布時,Service Worker 就把它看做此應(yīng)用的一個新版本。就算只修改了一個文件,也同樣如此。在任何一個給定的時間,Service Worker 可能會在它的緩存中擁有此應(yīng)用的多個版本,這幾個版本也都能用于提供服務(wù)。

要保持應(yīng)用的整體性,Angular Service Worker 會用所有的文件共同組成一個版本。組成版本的這些文件通常包括 HTML、JS 和 CSS 文件。把這些文件分成一組是至關(guān)重要的,因?yàn)樗鼈儠ハ嘁?,并且依賴于一些特定?nèi)容。比如,?index.html? 文件可能有個引用 ?bundle.js? 的 ?<script>? 標(biāo)簽,它可能會試圖從這個腳本中調(diào)用一個 ?startApp()? 函數(shù)。任何時候,只要這個版本的 ?index.html? 被提供了,與它對應(yīng)的 ?bundle.js? 也必須同時提供。這種情況下,使用調(diào)用了 ?startApp()? 的老的 ?index.html? 并同時使用定義了 ?runApp()? 的新 bundle 就是無效的。

當(dāng)使用惰性加載模塊時,文件的整體性就顯得格外重要。某個 JS 包可能引用很多惰性塊,而這些惰性塊的文件名在應(yīng)用的每次特定的構(gòu)建中都是唯一的。如果運(yùn)行應(yīng)用的 ?X? 版本視圖加載一個惰性塊,但該塊的服務(wù)器已經(jīng)升級到了 ?X + 1? 版本,這次惰性加載操作就會失敗。

本應(yīng)用的版本標(biāo)識符由其所有資源的內(nèi)容決定,如果它們中的任何一個發(fā)生了變化,則版本標(biāo)識符也隨之改變。實(shí)際上,版本是由 ?ngsw.json? 文件的內(nèi)容決定的,包含了所有已知內(nèi)容的哈希值。如果任何一個被緩存的文件發(fā)生了變化,則該文件的哈希也將在 ?ngsw.json? 中隨之變化,從而導(dǎo)致 Angular Service Worker 將這個活動文件的集合視為一個新版本。

?ngsw.json? 是在構(gòu)建時基于 ?ngsw-config.json? 生成的清單文件。

借助 Angular Service Worker 的這種版本控制行為,應(yīng)用服務(wù)器就可以確保這個 Angular 應(yīng)用中的這組文件始終保持一致。

更新檢測

每當(dāng)用戶打開或刷新應(yīng)用程序時,Angular Service Worker 都會通過查看清單(manifest)文件 “ngsw.json” 的更新來檢查該應(yīng)用程序的更新。如果它找到了更新,就會自動下載并緩存這個版本,并在下次加載應(yīng)用程序時提供。

資源整體性

長周期緩存的潛在副作用之一就是可能無意中緩存了無效的資源。在普通的 HTTP 緩存中,硬刷新或緩存過期限制了緩存這種無效文件導(dǎo)致的負(fù)面影響。而 Service Worker 會忽略這樣的約束,事實(shí)上會對整個應(yīng)用程序進(jìn)行長期緩存。因此,讓 Service Worker 獲得正確的內(nèi)容就顯得至關(guān)重要。

為了確保資源的整體性,Angular Service Worker 會驗(yàn)證所有帶哈希的資源的哈希值。通常,對于 ?Angular CLI? 應(yīng)用程序,用戶的 ?src/ngsw-config.json? 配置文件中會涵蓋 ?dist ?目錄下的所有內(nèi)容。

如果某個特定的文件未能通過驗(yàn)證,Angular Service Worker 就會嘗試用 “cache-busting” URL 為參數(shù)重新獲取內(nèi)容,以消除瀏覽器或中間緩存的影響。如果該內(nèi)容也未能通過驗(yàn)證,則 Service Worker 會認(rèn)為該應(yīng)用的整個版本都無效,并停止用它提供服務(wù)。如有必要,Service Worker 會進(jìn)入安全模式,這些請求將退化為直接訪問網(wǎng)絡(luò)。如果服務(wù)無效、損壞或內(nèi)容過期的風(fēng)險很高,則會選擇不使用緩存。
導(dǎo)致哈希值不匹配的原因有很多:

  • 在源服務(wù)器和最終用戶之間緩存圖層可能會提供陳舊的內(nèi)容。
  • 非原子化的部署可能會導(dǎo)致 Angular Service Worker 看到部分更新后的內(nèi)容。
  • 構(gòu)建過程中的錯誤可能會導(dǎo)致更新了資源,卻沒有更新 ?ngsw.json?。反之,也可能發(fā)生沒有更新資源,卻更新了 ?ngsw.json? 的情況。

不帶哈希的內(nèi)容

?ngsw.json? 清單中唯一帶哈希值的資源就是構(gòu)建清單時 ?dist ?目錄中的資源。而其它資源,特別是從 CDN 加載的資源,其內(nèi)容在構(gòu)建時是未知的,或者會比應(yīng)用程序部署得更頻繁。

如果 Angular Service Worker 沒有哈??梢则?yàn)證給定的資源,它仍然會緩存它的內(nèi)容,但會使用 “重新驗(yàn)證時失效” 的策略來承認(rèn) HTTP 緩存頭。也就是說,當(dāng)被緩存資源的 HTTP 緩存頭指出該資源已過期時,Angular Service Worker 將繼續(xù)提供內(nèi)容,并嘗試在后臺刷新資源。這樣,那些被破壞的非哈希資源留在緩存中的時間就不會超出為它配置的生命周期。

App 選項(xiàng)卡

如果應(yīng)用程序的資源版本突然發(fā)生了變化或沒有給出警告,就可能會有問題。

Angular Service Worker 會保證:正在運(yùn)行的應(yīng)用程序會繼續(xù)運(yùn)行和當(dāng)前應(yīng)用相同的版本。而如果在新的 Web 瀏覽器選項(xiàng)卡中打開了該應(yīng)用的另一個實(shí)例,則會提供該應(yīng)用的最新版本。因此,這個新標(biāo)簽可以和原始標(biāo)簽同時運(yùn)行不同版本的應(yīng)用。

重要:
這種擔(dān)保比普通的 Web 部署模型提供的擔(dān)保還要更強(qiáng)一點(diǎn)。 如果沒有 Service Worker,則不能保證稍后在這個正在運(yùn)行的應(yīng)用中惰性加載的代碼 和其初始代碼的版本是一樣的。

Angular Service Worker 為什么可能會更改運(yùn)行中的應(yīng)用的版本有幾個有限的原因。 其中一些是因?yàn)槌鲥e了:

  • 由于哈希驗(yàn)證失敗,當(dāng)前版本變成了無效的
  • 某個無關(guān)的錯誤導(dǎo)致 Service Worker 進(jìn)入了安全模式,或者說,它被暫時禁用了

Angular Service Worker 能知道在任何指定的時刻正在使用哪些版本,并清除那些沒有被任何選項(xiàng)卡使用的版本。

另一些可能導(dǎo)致 Angular Service Worker 在運(yùn)行期間改變版本的因素是一些正常事件:

  • 頁面被重新加載/刷新。
  • 該頁面通過 ?SwUpdate ?服務(wù)請求立即激活這個更新。

Service Worker 更新

Angular Service Worker 是一個運(yùn)行在 Web 瀏覽器中的小腳本。有時,這個 Service Worker 也可能會需要更新,以修復(fù)錯誤和增強(qiáng)特性。

首次打開應(yīng)用時或在一段非活動時間之后再訪問應(yīng)用程序時,就會下載 Angular Service Worker。如果 Service Worker 發(fā)生了變化,Service Worker 就會在后臺進(jìn)行更新。

Angular Service Worker 的大部分更新對應(yīng)用程序來說都是透明的 - 舊緩存仍然有效,其內(nèi)容仍然能正常使用。但是,在 Angular Service Worker 中可能偶爾會有錯誤修復(fù)或新功能,需要讓舊的緩存失效。這時,應(yīng)用程序就從會網(wǎng)絡(luò)上透明地進(jìn)行刷新。

繞過 Service Worker

某些情況下,你可能想要完全繞過 Service Worker,轉(zhuǎn)而讓瀏覽器處理請求。比如當(dāng)你要用到某些 Service Worker 尚不支持的特性時(比如報告文件上傳的進(jìn)度)。

要想繞過 Service Worker,你可以設(shè)置一個名叫 ?ngsw-bypass? 的請求頭或查詢參數(shù)。(這個請求頭或查詢參數(shù)的值會被忽略,可以把它設(shè)為空字符串或略去。)

調(diào)試 Angular Service Worker

偶爾,可能會需要檢查運(yùn)行中的 Angular Service Worker,以調(diào)查問題或確保它在按設(shè)計(jì)運(yùn)行。瀏覽器提供了用于調(diào)試 Service Worker 的內(nèi)置工具,而且 Angular Service Worker 本身也包含了一些有用的調(diào)試功能。

定位并分析調(diào)試信息

Angular Service Worker 會在虛擬目錄 ?ngsw/? 下暴露出調(diào)試信息。目前,它暴露的唯一的 URL 是 ?ngsw/state?。下面是這個調(diào)試頁面中的一段范例內(nèi)容:

NGSW Debug Info:

Driver version: 13.3.7
Driver state: NORMAL ((nominal))
Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c
Last update check: never

=== Version eea7f5f464f90789b621170af5a569d6be077e5c ===

Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65

=== Idle Task Queue ===
Last update tick: 1s496u
Last update run: never
Task queue:
 * init post-load (update, cleanup)

Debug log:

驅(qū)動程序的狀態(tài)

第一行表示驅(qū)動程序的狀態(tài):

Driver state: NORMAL ((nominal))

?NORMAL ?表示這個 Service Worker 正在正常運(yùn)行,并且沒有處于降級運(yùn)行的狀態(tài)。

有兩種可能的降級狀態(tài):

降級狀態(tài)

詳情

EXISTING_CLIENTS_ONLY

這個 Service Worker 沒有該應(yīng)用的最新已知版本的干凈副本。較舊的緩存版本可以被安全的使用,所以現(xiàn)有的選項(xiàng)卡將繼續(xù)使用較舊的版本運(yùn)行本應(yīng)用,但新的應(yīng)用將從網(wǎng)絡(luò)上加載。

SAFE_MODE

Service Worker 不能保證使用緩存數(shù)據(jù)的安全性。發(fā)生了意外錯誤或所有緩存版本都無效。這時所有的流量都將從網(wǎng)絡(luò)提供,盡量少運(yùn)行 Service Worker 中的代碼。

在這兩種情況下,后面的括號注解中都會提供導(dǎo)致 Service Worker 進(jìn)入降級狀態(tài)的錯誤信息。

這兩種狀態(tài)都是暫時的;它們僅在 ServiceWorker 實(shí)例 的生命周期內(nèi)保存。 瀏覽器有時會終止空閑的 Service Worker,以節(jié)省內(nèi)存和處理能力,并創(chuàng)建一個新的 Service Worker 實(shí)例來響應(yīng)網(wǎng)絡(luò)事件。 無論先前實(shí)例的狀態(tài)如何,新實(shí)例均以 ?NORMAL ?模式啟動。

最新清單的哈希

Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c

這是 Service Worker 所知道的應(yīng)用最新版本的 SHA1 哈希值。

最后一次更新檢查

Last update check: never

這表示 Service Worker 最后一次檢查應(yīng)用程序的新版本或更新的時間?!皀ever” 表示 Service Worker 從未檢查過更新。

在這個調(diào)試文件范例中,這次更新檢查目前是已排期的,如下一節(jié)所述。

版本

=== Version eea7f5f464f90789b621170af5a569d6be077e5c ===

Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65

在這個例子中,Service Worker 擁有一個版本的應(yīng)用程序緩存并用它服務(wù)于兩個不同的選項(xiàng)卡。

注意:
這個版本哈希值是上面列出的“最新清單的哈?!薄?nbsp;它的兩個客戶運(yùn)行的都是最新版本。每個客戶都用瀏覽器中 ?Clients ?API 的 ID 列了出來。

空閑任務(wù)隊(duì)列

=== Idle Task Queue ===
Last update tick: 1s496u
Last update run: never
Task queue:
 * init post-load (update, cleanup)

空閑任務(wù)隊(duì)列是 Service Worker 中所有在后臺發(fā)生的未決任務(wù)的隊(duì)列。如果這個隊(duì)列中存在任何任務(wù),則列出它們的描述。在這個例子中,Service Worker 安排的任務(wù)是一個用于更新檢查和清除過期緩存的后期初始化操作。

最后的 tick/run 計(jì)數(shù)器給出了與特定事件發(fā)生有關(guān)的空閑隊(duì)列中的時間?!癓ast update run” 計(jì)數(shù)器顯示的是上次執(zhí)行空閑任務(wù)的時間?!癓ast update tick” 顯示的是自上次事件以來可能要處理的隊(duì)列的時間。

調(diào)試日志

Debug log:

在 Service Worker 中出現(xiàn)的任何錯誤都會記錄在這里。

開發(fā)者工具

Chrome 等瀏覽器提供了能與 Service Worker 交互的開發(fā)者工具。這些工具在使用得當(dāng)時非常強(qiáng)大,但也要牢記一些事情。

  • 使用開發(fā)人員工具時,Service Worker 將繼續(xù)在后臺運(yùn)行,并且不會重新啟動。這可能會導(dǎo)致開著 Dev Tools 時的行為與用戶實(shí)際遇到的行為不一樣。
  • 如果你查看緩存存儲器的查看器,緩存就會經(jīng)常過期。右鍵單擊緩存存儲器的標(biāo)題并刷新緩存。
  • 在 Service Worker 頁停止并重新啟動這個 Service Worker 將會觸發(fā)一次更新檢查。

Service Worker 的安全性

像任何復(fù)雜的系統(tǒng)一樣,錯誤或損壞的配置可能會導(dǎo)致 Angular Service Worker 以不可預(yù)知的方式工作。雖然它在設(shè)計(jì)時就嘗試將此類問題的影響降至最低,但是,如果管理員需要快速停用 Service Worker,Angular Service Worker 也包含多種故障保護(hù)機(jī)制。

故障保護(hù)機(jī)制

要停用 Service Worker,請刪除或重命名 ?ngsw.json? 文件。當(dāng) Service Worker 對 ?ngsw.json? 的請求返回 ?404 ?時,Service Worker 就會刪除它的所有緩存并注銷自己,本質(zhì)上就是自毀。

安全工作者

?@angular/service-worker? NPM 包中還包含一個小腳本 ?safety-worker.js?,當(dāng)它被加載時就會把它自己從瀏覽器中注銷,并移除此 Service Worker 的緩存。這個腳本可以作為終極武器來擺脫那些已經(jīng)安裝在客戶端頁面上的不想要的 Service Worker。

重要:
你不能直接注冊這個 Safety Worker,因?yàn)榫哂幸丫彺鏍顟B(tài)的舊客戶端可能無法看到一個新的、用來安裝 另一個 worker 腳本的 ?index.html?。

你必須在想要注銷的 Service Worker 腳本的 URL 中提供 ?safety-worker.js? 的內(nèi)容, 而且必須持續(xù)這樣做,直到確定所有用戶都已成功注銷了原有的 Worker。 對大多數(shù)網(wǎng)站而言,這意味著你應(yīng)該永遠(yuǎn)為舊的 Service Worker URL 提供 這個 Safety Worker。 這個腳本可以用來停用 ?@angular/service-worker?(并移除相應(yīng)的緩存)以及任何其它曾在你的站點(diǎn)上提供過的 Service Worker。

更改應(yīng)用的位置

重要:
Service Worker 無法在重定向后工作。你可能已經(jīng)遇到過這種錯誤:?The script resource is behind a redirect, which is disallowed?。

如果你不得不更改應(yīng)用的位置,就可能會出現(xiàn)問題。如果你設(shè)置了從舊位置(比如 ?example.com?)到新位置(比如 ?www.example.com?)的重定向,則 Service Worker 將停止工作。同樣,對于完全從 Service Worker 加載該網(wǎng)站的用戶,甚至都不會觸發(fā)重定向。老的 Worker(注冊在 ?example.com?)會嘗試更新并將請求發(fā)送到原來的位置 ?example.com?,該位置重定向到新位置 ?www.example.com? 就會導(dǎo)致錯誤 ?The script resource is behind a redirect, which is disallowed?。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號