「PWA」Workbox-Window v4.x 中文版

Workbox 目前發了一個大版本,從 v3.x 到了 v4.x,變化有挺大的,下面是在 window 環境下的模塊。javascript


什麼是 workbox-window?

workbox-window 包是一組模塊,用於在 window 上下文中運行,也就是說,在你的網頁內部運行。 它們是 servicewoker 中運行的其餘 workbox 的補充。html

workbox-window的主要功能/目標是:java

  • 經過幫助開發人員肯定 serviceWorker 生命週期中最關鍵的時刻,並簡化對這些時刻的響應,簡化 serviceWoker 註冊和更新的過程。
  • 幫助防止開發人員犯下最多見的錯誤。
  • 使 serviceWorker 程序中運行的代碼與 window 中運行的代碼之間的通訊更加輕鬆。

導入和使用 workbox-window

workbox-window 包的主要入口點是 Workbox 類,你能夠從咱們的CDN或使用任何流行的 JavaScript 打包工具將其導入代碼中。node

使用咱們的 CDN

在您的網站上導入 Workbox 類的最簡單方法是從咱們的 CDN:webpack

<script type="module"> import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-window.prod.mjs'; if ('serviceWorker' in navigator) { const wb = new Workbox('/sw.js'); wb.register(); } </script>
複製代碼

注意,此示例使用 <script type ="module">import 語句來加載 Workbox 類。 雖然您可能認爲須要轉換此代碼以使其在舊版瀏覽器中運行,但實際上並非必需的。git

支持 serviceWorker 的全部主要瀏覽器也支持 JavaScript 模塊,所以將此代碼提供給任何瀏覽器都是完美的(舊版瀏覽器將忽略它)。es6

經過 JavScript 打包加載 Workbox

雖然使用 Workbox 絕對不須要工具,但若是您的開發基礎架構已經包含了與 npm 依賴項一塊兒使用的 webpackRollup 等打包工具,則可使用它們來加載 Workboxgithub

第一步就是安裝 Workbox 作爲你應用的依賴:web

npm install workbox-window
複製代碼

而後,在您的某個應用程序的 JavaScript 文件中,經過引用 workbox-window 包名稱導入 Workboxnpm

import {Workbox} from 'workbox-window';

if ('serviceWorker' in navigator) {
  const wb = new Workbox('/sw.js');

  wb.register();
}
複製代碼

若是您的打包工具支持經過動態 import 語句進行代碼拆分,你還能夠有條件地加載workbox-window,這有助於減小頁面主包的大小。

儘管 Workbox 很是小(1kb gzip壓縮),可是沒有理由須要加載站點的核心應用程序邏輯,由於 serviceWorker 本質上是漸進式加強。

if ('serviceWorker' in navigator) {
  const {Workbox} = await import('workbox-window');

  const wb = new Workbox('/sw.js');
  wb.register();
}
複製代碼

高級打包概念

與在 Service worker 中運行的 Workbox 包不一樣,workbox-windowpackage.json 中的 mainmodule 字段引用的構建文件被轉換爲 ES5。 這使它們與當今的構建工具兼容 - 其中一些不容許開發人員轉換其 node_module 依賴項的任何內容。

若是你的構建系統容許您轉換依賴項(或者若是您不須要轉換任何代碼),那麼最好導入特定的源文件而不是包自己。

如下是你能夠導入 Workbox 的各類方法,以及每一個方法將返回的內容的說明:

// 使用ES5語法導入UMD版本
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');

// 使用ES5語法導入模塊版本
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';

// 使用ES2015 +語法導入模塊源文件
import {Workbox} from 'workbox-window/Workbox.mjs';
複製代碼

重要! 若是您直接導入源文件,則還須要配置構建過程以縮小文件,並在將其部署到生產時刪除僅開發代碼。 有關詳細信息,請參閱使用打包(webpack / Rollup)和Workbox的指南

示例

導入 Workbox 類後,可使用它來註冊 serviceWorker 並與之交互。 如下是您能夠在應用程序中使用 Workbox 的一些示例:

註冊 serviceWorker 並在 serviceWorker 第一次處於 active 狀態時通知用戶:

許多 Web 應用程序用戶 serviceWorker 預緩存資源,以便其應用程序在後續頁面加載時離線工做。在某些狀況下,通知用戶該應用程序如今能夠離線使用是有意義的。

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', (event) => {
  // 若是另外一個版本的 serviceWorker,`event.isUpdate`將爲true
  // 當這個版本註冊時,worker 正在控制頁面。
  if (!event.isUpdate) {
    console.log('Service worker 第一次激活!');

    // 若是您的 serviceWorker 配置爲預緩存資源,那麼
    // 資源如今都應該可用。
  }
});

// 添加事件偵聽器後註冊 serviceWorker 。
wb.register();
複製代碼

若是 serviceWorker 已安裝但等待激活,則通知用戶

當由現有 serviceWorker 控制的頁面註冊新的 serviceWorker 時,默認狀況下,在初始 serviceWorker 控制的全部客戶端徹底卸載以前,serviceWorker 將不會激活。

這是開發人員常見的混淆源,特別是在從新加載當前頁面不會致使新 serviceWorker 程序激活的狀況下。

爲了幫助減小混淆並在發生這種狀況時明確說明,Workbox 類提供了一個能夠監聽的等待事件:

const wb = new Workbox('/sw.js');

wb.addEventListener('waiting', (event) => {
  console.log(`已安裝新的 serviceWorker,但沒法激活` +
      `直到運行當前版本的全部選項卡都已徹底卸載。`);
});

// 添加事件偵聽器後註冊 service worker 。
wb.register();
複製代碼

從 workbox-broadcast-update 包通知用戶緩存更新

workbox-broadcast-update 包很是棒

可以從緩存中提供內容(快速交付)的方式,同時還可以通知用戶該內容的更新(使用stale-while-revalidate 策略)。

要從 window 接收這些更新,您能夠偵聽 CACHE_UPDATE 類型的消息事件:

const wb = new Workbox('/sw.js');

wb.addEventListener('message', (event) => {
  if (event.data.type === 'CACHE_UPDATE') {
    const {updatedURL} = event.data.payload;

    console.log(`${updatedURL} 的更新版本可用`);
  }
});

// 添加事件偵聽器後註冊 service worker。
wb.register();
複製代碼

向 serviceWorker 發送要緩存的URL列表

對於某些應用程序,能夠知道在構建時須要預先緩存的全部資源,但某些應用程序根據用戶首先登錄的 URL 提供徹底不一樣的頁面。

對於後一類別的應用程序,僅緩存用戶所訪問的特定頁面所需的資源多是有意義的。 使用 workbox-routing 軟件包時,您能夠向路由器發送一個 URL 列表進行緩存,它將根據路由器自己定義的規則緩存這些 URL。

每當激活新的 serviceWorker 時,此示例都會將頁面加載的 URL 列表發送到路由器。 請注意,發送全部 URL 是能夠的,由於只會緩存與 serviceWorker 中定義的路由匹配的 URL:

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', (event) => {
  // 獲取當前頁面URL +頁面加載的全部資源。
  const urlsToCache = [
    location.href,
    ...performance.getEntriesByType('resource').map((r) => r.name),
  ];
  // 將該URL列表發送到 serviceWorker 的路由器。
  wb.messageSW({
    type: 'CACHE_URLS',
    payload: {urlsToCache},
  });
});

// 添加事件偵聽器後註冊 serviceWorker。
wb.register();
複製代碼

注意:上述技術適用於經過默認路由器上的 workbox.routing.registerRoute() 方法定義的任何路由。 若是您要建立本身的路由器實例,則須要手動調用 addCacheListener() 。

重要的 serviceWorker 生命週期

serviceWorker 的生命週期很複雜,徹底能夠理解。 它之因此如此複雜,部分緣由在於它必須處理 serviceWorker 全部可能使用的全部邊緣狀況(例如,註冊多個 serviceWorker,在不一樣的框架中註冊不一樣的 serviceWorker,註冊具備不一樣名稱的 serviceWorker 等)。

可是大多數實現 serviceWorker 的開發人員不該該擔憂全部這些邊緣狀況,由於它們的使用很是簡單。 大多數開發人員每頁加載只註冊一個 serviceWorker,而且他們不會更改他們部署到服務器的 serviceWorker 文件的名稱。

Workbox 類經過將全部 serviceWorker 註冊分爲兩類來包含 serviceWorker 生命週期的這個更簡單的視圖:實例本身的註冊 serviceWorker 和外部 serviceWorker:

  • 註冊 serviceWorker:因爲 Workbox 實例調用 register() 而已開始安裝的 serviceWorker,或者若是調用 register() 未在註冊時觸發 updatefound 事件,則已啓用安裝 serviceWorker。
  • 外部 serviceWorker:一個 serviceWorker,開始獨立於 Workbox 實例調用 register() 安裝。 當用戶在另外一個標籤頁中打開新版本的網站時,一般會發生這種狀況。

咱們的想法是,來自 serviceWorker 的全部生命週期事件都是你的代碼應該期待的事件,而來自外部 serviceWorker 的全部生命週期事件都應該被視爲具備潛在危險,而且應該相應地警告用戶。

考慮到這兩類 serviceWorker,下面是全部重要serviceWorker 生命週期時刻的細分,以及開發人員如何處理它們的建議:

第一次安裝 serviceWorker

你可能但願在 serviceWorker 第一次安裝時不一樣於處理全部將來更新的方式。

在 Workbox 中,你能夠經過檢查如下任何事件的 isUpdate 屬性來區分版本首次安裝和將來更新。 對於第一次安裝,isUpdate 將爲 false。

const wb = new Workbox('/sw.js');

wb.addEventListener('installed', (event) => {
  if (!event.isUpdate) {
    // 在這裏編寫第一次安裝須要的代碼
  }
});

wb.register();
複製代碼
時刻 事件 建議操做
新的 serviceWorker 已安裝(第一次) installed serviceWorker 第一次安裝時,一般會預先緩存網站離線工做所需的全部資源。 你能夠考慮通知用戶他們的站點如今能夠離線運行。

此外,因爲 serviceWorker 第一次安裝它時不會截獲該頁面加載的獲取事件,你也能夠考慮緩存已加載的資源(儘管若是這些資源已經被預先緩存,則不須要這樣作)。 向上面的緩存示例發送 serviceWorker 的URL列表顯示瞭如何執行此操做。
serviceWorker 已經控制頁面 controlling 安裝新 serviceWorker 程序並開始控制頁面後,全部後續獲取事件都將經過該 serviceWorker 程序。 若是你的 serviceWorker 添加了任何特殊邏輯來處理特定的 fetch 事件,那麼當你知道邏輯將運行時就是這一點。

請注意,第一次安裝 serviceWorker 時,它不會開始控制當前頁面,除非該 serviceWorker 在其 activate 事件中調用 clients.claim()。 默認行爲是等到下一頁加載開始控制。

workbox-window 的角度來看,這意味着僅在 serviceWorker 調用 clients.claim() 的狀況下才調度 controlling 事件。 若是在註冊以前已經控制了頁面,則不會調度此事件。
serviceWorker 已經完成激活 activated 如上所述,serviceWorker 第一次完成激活它可能(或可能不)已經開始控制頁面。

所以,你不該該將 activate 事件視爲了解 serviceWorker 什麼時候控制頁面的方式。 可是,若是你在活動事件中(在 serviceWorker )運行邏輯,而且你須要知道該邏輯什麼時候完成,則激活的事件將讓你知道。

發現 serviceWorker 的更新版本時

當新 serviceWorker 開始安裝但現有版本當前正在控制該頁面時,如下全部事件的 isUpdate 屬性都將爲 true。

在這種狀況下,你的反應一般與第一次安裝不一樣,由於你必須管理用戶什麼時候以及如何得到此更新。

時刻 事件 建議操做
已安裝新 serviceWorker(更新前一個) installed 若是這不是第一個 serviceWorker 安裝(event.isUpdate === true),則表示已找到並安裝了較新版本的 serviceWorker(即,與當前控制頁面的版本不一樣)。

這一般意味着已將更新版本的站點部署到你的服務器,而且新資源可能剛剛完成預先緩存。

注意:某些開發人員使用已安裝的事件來通知用戶其新版本的站點可用。 可是,根據我是否在安裝 serviceWorker 程序中調用 skipWaiting(),安裝的 serviceWorker 可能會當即生效,也可能不會當即生效。 若是你確實調用 skipWaiting(),那麼最好在新 serviceWorker 激活後通知用戶更新,若是你沒有調用 skipWaiting,最好通知他們等待事件中的掛起更新(見下文了解更多信息) 細節)。
serviceWorker 已安裝,但它仍處於等待階段 waiting 若是 serviceWorker 的更新版本在安裝時未調用skipWaiting(),則在當前活動 serviceWorker 控制的全部頁面都已卸載以前,它不會激活。 你可能但願通知用戶更新可用,並將在下次訪問時應用。

警告! 開發人員一般會提示用戶從新加載以獲取更新,但在許多狀況下刷新頁面不會激活已安裝的工做程序。 若是用戶刷新頁面而且serviceWorker 仍在等待,則等待事件將再次觸發,而且 event.wasWaitingBeforeRegister 屬性將爲 true。 請注意,咱們計劃在未來的版本中改進此體驗。 關注問題#1848以獲取更新。

另外一種選擇是提示用戶並詢問他們是否想要得到更新或繼續等待。 若是選擇獲取更新,則可使用 postMessage() 告訴 serviceWorker 運行 skipWaiting()。 有關示例,請參閱高級配方爲用戶提供頁面從新加載。
serviceWorker 已開始控制頁面 controlling 當更新的 serviceWorker 開始控制頁面時,這意味着當前控制的 serviceWorker 的版本與加載頁面時控制的版本不一樣。 在某些狀況下可能沒問題,但也可能意味着當前頁面引用的某些資源再也不位於緩存中(也可能不在服務器上)。 你可能須要考慮通知用戶頁面的某些部分可能沒法正常工做。

注意:若是不在serviceWorker 中調用 skipWaiting(),則不會觸發控制事件。
serviceWorker 已完成激活 activated 當更新的 serviceWorker 完成激活時,這意味着你在 serviceWorker 的激活中運行的任何邏輯都已完成。 若是有什麼須要延遲,直到邏輯完成,這是運行它的時間。

找到意外版本的 serviceWorker

有時用戶會在很長一段時間內在後臺標籤中打開你的網站。 他們甚至可能會打開一個新標籤並導航到你的網站,卻沒有意識到他們已經在後臺標籤中打開了您的網站。 在這種狀況下,您的網站可能同時運行兩個版本,這可能會爲開發人員帶來一些有趣的問題。

考慮這樣一種狀況,即您的網站的標籤 A 正在運行 v1,標籤 B 正在運行 v2。 加載選項卡 B 時,它將由 v1 附帶的 serviceWorker 版本控制,但服務器返回的頁面(若是使用網絡優先緩存策略用於導航請求)將包含全部 v2 資源。

這對於選項卡 B 來講一般不是問題,由於當你編寫 v2 代碼時,你知道你的 v1 代碼是如何工做的。可是,它多是標籤A的問題,由於你的 v1 代碼沒法預測你的 v2 代碼可能會引入哪些更改。

爲了幫助處理這些狀況,workbox-window 還會在檢測到來自「外部」 serviceWorker 的更新時調度生命週期事件,其中 external 表示任何不是當前 Workbox 實例註冊的版本。

時刻 事件 建議操做
已安裝外部 serviceWorker externalinstalled 若是已安裝外部 serviceWorker,則可能意味着用戶在不一樣的選項卡中運行你網站的較新版本。

如何響應可能取決於已安裝的服務是進入等待仍是活動階段。
經過等待激活來安裝外部 serviceWorker externalwaiting 若是外部 serviceWorker 正在等待激活,則可能意味着用戶試圖在另外一個選項卡中獲取你網站的新版本,可是因爲此選項卡仍處於打開狀態,所以他們已被阻止。

若是發生這種狀況,你能夠考慮向用戶顯示通知,要求他們關閉此標籤。 在極端狀況下,你甚至能夠考慮調用 window.reload(),若是這樣作不會致使用戶丟失任何已保存的狀態。
serviceWorker 外部 serviceWorker 已激活 externalactivated 若是外部 serviceWorker 程序已激活,則當前頁面極可能沒法繼續正常運行。 你可能須要考慮向用戶顯示他們正在運行舊版本頁面的通知,而且可能會出現問題。

避免常見錯誤

Workbox 提供的最有用的功能之一是它的開發人員日誌記錄。 對於 worbox-window 也是這樣。

咱們知道與 serviceWorker 一塊兒開發每每會讓人感到困惑,當事情發生與你指望的相反時,很難知道緣由。

例如,當你對 serviceWorker 進行更改並從新加載頁面時,你可能沒法在瀏覽器中看到該更改。 最可能的緣由是,你的 serviceWorker 仍在等待激活。

可是當使用 Workbox 類註冊 serviceWorker 時,你將被告知開發人員控制檯中的全部生命週期狀態更改,這應該有助於調試爲何事情不像你指望的那樣。

此外,開發人員在首次使用 serviceWorker 時常犯的錯誤是在錯誤的範圍內註冊 serviceWorker。

爲了防止這種狀況發生,Workbox類將警告您註冊服務工做者的頁面是否不在該服務工做者的範圍內。 若是您的服務工做者處於活動狀態但還沒有控制該頁面,它還會警告你:

window 到 serviceWorker 的溝通

大多數高級 serviceWorker 使用涉及 serviceWorker 和 window 之間的消息傳遞丟失。 Workbox 類經過提供 messageSW() 方法來幫助解決這個問題,該方法將postMessage() 實例的註冊 serviceWorker 並等待響應。

雖然你能夠以任何格式向 serviceWorker 發送數據,但全部 Workbox 包共享的格式是具備三個屬性的對象(後兩個是可選的):

屬性 必須 類型 描述
type string 標識此消息的惟一字符串。

按照慣例,類型都是大寫的,下劃線分隔單詞。 若是類型表示要採起的動做,則它應該是如今時的命令(例如 CACHE_URLS ),若是類型表示報告的信息,則它應該是過去時(例如 URLS_CACHED )。
meta string 在 Workbox 中,這始終是發送消息的 Workbox 包的名稱。 本身發送郵件時,能夠省略此屬性或將其設置爲你喜歡的任何內容。
payload * 正在發送的數據。 一般這是一個對象,但它不必定是。

經過 messageSW() 方法發送的消息使用 MessageChannel,所以接收方能夠響應它們。 要響應消息,你能夠在消息事件偵聽器中調用 event.ports[0].postMessage(response)。 messageSW() 方法返回一個 promise,該 promise 將解析爲你返回的任何響應。

這是一個從 window 到 serviceWorker 發送消息並得到響應的示例。 第一個代碼塊是 serviceWorker 中的消息偵聽器,第二個塊使用 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 中的代碼(運行在 window 環境):

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
複製代碼

管理版本不兼容性

上面的示例顯示瞭如何從 window 中實現檢查 serviceWorker 版本。 使用此示例是由於當你在 window 和 serviceWorker 之間來回發送消息時,請務必注意你的 serviceWorker 可能沒有運行與你的頁面代碼運行相同的站點版本,以及 處理此問題的解決方案會有所不一樣,具體取決於你是以網絡優先服務仍是緩存優先服務。

網絡優先

首先爲你的網頁提供服務時,你的用戶將始終從你的服務器獲取最新版本的 HTML。 可是,當用戶第一次從新訪問你的站點時(在部署更新以後),他們得到的 HTML 將是最新版本,但在其瀏覽器中運行的 serviceWorker 將是先前安裝的版本(多是許多舊版本)。

理解這種可能性很是重要,由於若是當前版本的頁面加載的 JavaScript 向舊版本的 serviceWorker 發送消息,則該版本可能不知道如何響應(或者它可能以不兼容的格式響應)。

所以,在進行任何關鍵工做以前,始終對 serviceWorker 進行版本控制並檢查兼容版本是個好主意。

例如,在上面的代碼中,若是該 messageSW() 調用返回的 serviceWorker 版本早於預期版本,則最好等到找到更新(這應該在調用 register() 時發生)。 此時,你能夠通知用戶或更新,也能夠手動跳過等待階段以當即激活新的 serviceWorker。

緩存優先

與在網絡服務頁面時相比,首先,當你首先提供頁面緩存時,你知道你的頁面最初將始終與 serviceWorker 的版本相同(由於這是服務它的緣由)。 所以,當即使用messageSW() 是安全的。

可是,若是找到 serviceWorker 的更新版本並在頁面調用 register() 時激活(即你有意跳過等待階段),則向其發送消息可能再也不安全。

管理這種可能性的一種策略是使用版本控制方案,容許你區分中斷更新和非中斷更新,而且在更新中斷的狀況下,你知道向 serviceWorker 發送消息是不安全的。 相反,你須要警告用戶他們正在運行舊版本的頁面,並建議他們從新加載以獲取更新。


博客名稱:王樂平博客

CSDN博客地址:blog.csdn.net/lecepin

知識共享許可協議
本做品採用 知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。
相關文章
相關標籤/搜索