[譯] Service workers:Progressive Web Apps 背後的小英雄

Fate_輕舞飛揚發表於2018-03-05

Service workers:Progressive Web Apps 背後的小英雄

[譯] Service workers:Progressive Web Apps 背後的小英雄

Service workers 是 Progressive Web Apps 的核心。它們允許快取資源和推送通知,這是原生 app 應用的兩個突出特性。

service worker 是你的網頁和網路之間的 可程式設計代理,它可以攔截和快取網路請求。這實際上可以讓你 使自己的 app 具有離線優先的體驗

Service workers 是一種特殊的 web worker:一個關聯工作環境上執行的網頁且與主執行緒分離的 JavaScript 檔案。它帶來了非阻塞這一優點 —— 所以計算處理可以在不犧牲 UI 響應的情況下完成。

因為它在單獨的執行緒上,因此它沒有訪問 DOM 的許可權,也沒有訪問本地儲存 APIs 和 XHR API 的許可權。它只能使用 Channel Messaging API 與主執行緒通訊。

Service Workers 與其他新進的 Web APIs 搭配:

  • Promises
  • Fetch API
  • Cache API

它們 只在使用 HTTPS 協議的頁面可用(除了本地請求不需要安全連線,這會使測試更簡單。)。

後臺執行

Service workers 獨立執行,當與其相關聯的應用沒有執行的時候也可以接收訊息。

它們可以後臺執行的幾種情況:

  • 當你的手機應用 在後臺執行,沒有啟用
  • 當你的手機應用 關閉 甚至沒有在後臺執行
  • 瀏覽器關閉,如果 app 執行在瀏覽器上

service workers 非常有用的幾種場景:

  • 它們可以作為快取層來處理網路請求,並且快取離線時要使用的內容
  • 它們允許推送通知

service worker 只有在需要的時候執行,不然則停止執行。

離線支援

傳統上,web app 的離線體驗一直很差。沒有網路,web app 通常根本無法工作。另一方面,原生手機 app 則有能力提供一種可以離線執行的版本或者友好的訊息提示。

這就不是一種友好的訊息提示,但這是 Chrome 中一個網頁在沒有網路連線情況下的樣子:

[譯] Service workers:Progressive Web Apps 背後的小英雄

可能唯一的好處就是你可以點選恐龍來玩免費的小遊戲 —— 但這很快就會變的無聊。

[譯] Service workers:Progressive Web Apps 背後的小英雄

最近,HTML5 AppCache 已經承諾允許 web apps 快取資源和離線工作。但是它缺乏靈活性,而且混亂的表現也讓它不足勝任這項工作(並已經停止)。

Service workers 是新的離線快取標準。

可以進行哪種快取?

在安裝期間預快取資源

可以在第一次開啟 app 的時候安裝在整個應用中重用的資源,如圖片,CSS,JavaScript 檔案。

這就給出了所謂的 App Shell 體系

快取網路請求

使用 Fetch API,我們可以編輯來自伺服器的響應,如果伺服器無法訪問,可以從快取中提供響應作為替代。

Service Worker 生命週期

service worker 經過以下三個步驟才能提供完整的功能:

  • 註冊
  • 安裝
  • 啟用

註冊

註冊告訴瀏覽器 service worker 在哪裡,並在後臺開始安裝。

註冊放置在 worker.js 中 service worker 的示例程式碼:

if ('serviceWorker' in navigator) { 
  window.addEventListener('load', () => {   
    navigator.serviceWorker.register('/worker.js') 
    .then((registration) => { 
      console.log('Service Worker registration completed with scope: ', registration.scope) 
    }, (err) => { 
      console.log('Service Worker registration failed', err)
    })
  })
} else { 
  console.log('Service Workers not supported') 
}
複製程式碼

即使此程式碼被多次呼叫,如果 service worker 是新的,並且以前沒有被註冊,或者已更新,瀏覽器將僅執行註冊。

作用域

register() 呼叫還接受一個作用域引數,該引數是一個路徑用來確定應用程式的哪一部分可以由 service worker 控制。

它預設包含 service worker 的資料夾中的所有檔案和子資料夾,所以如果將它放到根資料夾,它將控制整個 app。在子資料夾中,它將只會控制當前路徑下的頁面。

下面的示例通過指定 /notifications/ 資料夾範圍來註冊 service worker。

navigator.serviceWorker.register('/worker.js', { 
  scope: '/notifications/' 
})
複製程式碼

/ 很重要:在這種情況下,頁面 /notifications 不會觸發 service worker,而如果作用域是:

{ scope: '/notifications' }
複製程式碼

它就會起作用。

注意:service worker 不能從一個資料夾中“提升”自己的作用域:如果它的檔案放在 /notifications 下,它不能控制 / 路徑或其他不在 /notifications 下的路徑。

安裝

如果瀏覽器確定 service worker 過期或者以前從未註冊過,則會繼續安裝。

self.addEventListener('install', (event) => { 
  //... 
});
複製程式碼

這是使用 service worker 初始化快取的好時機。然後使用 Cache API 快取 App Shell 和靜態資源。

啟用

一旦 service worker 被成功註冊和安裝,第三步就是啟用。

這時,當介面載入時,service worker 就能正常工作了。

它不能和已經載入的頁面進行互動,因此 service worker 只有在使用者和應用互動的第二次或重新載入已開啟的頁面時才有用。

self.addEventListener('activate', (event) => { 
  //... 
});
複製程式碼

這個事件的一個好的用例是清除舊快取和一些關聯到舊版本並且沒有被新版本的 service worker 使用的檔案。

更新 Service Worker

要更新 service worker,你只需修改其中的一個位元組。當暫存器程式碼執行的時候,它就會被更新。

一旦更新了 service worker,直到所有關聯到舊版本 service worker 已載入的頁面全部關閉,新的 service worker 才會起作用。

這確保了在已經工作的應用/頁面上不會有任何中斷。

重新整理頁面還不夠,因為舊的 worker 仍在執行,且沒有被刪除。

Fetch 事件

當網路請求資源時 fetch 事件 被觸發。

這給我們提供了在發起網路請求前檢視快取的能力。

例如,下面的程式碼片段使用 Cache API 來檢查請求的 URL 是否已經儲存在快取響應裡面。如果已存在,它會返回快取中的響應。否則,它會執行 fetch 請求並返回結果。

self.addEventListener('fetch', (event) => {
  event.respondWith( 
    caches.match(event.request) 
      .then((response) => { 
        if (response) { 
          //entry found in cache 
          return response 
        } 
        return fetch(event.request) 
      } 
    ) 
  ) 
})
複製程式碼

後臺同步

後臺同步允許發出的連線延遲,直到使用者有可用的網路連線。

這是確保使用者能離線使用 app,能對其進行操作,並且當網路連線時排隊進行服務端更新(而不是顯示嘗試獲取訊號的無限旋轉圈)的關鍵。

navigator.serviceWorker.ready.then((swRegistration) => { 
  return swRegistration.sync.register('event1') 
});
複製程式碼

這段程式碼監聽 service worker 中的事件:

self.addEventListener('sync', (event) => { 
  if (event.tag == 'event1') { 
    event.waitUntil(doSomething()) 
  } 
})
複製程式碼

doSomething() 返回一個 promise 物件。如果失敗,另一個同步事件將安排自動重試,直到成功。

這也允許應用程式在有可用網路連線時,立即從伺服器更新資料。

推送事件

Service workers 讓 web apps 為使用者提供本地推送。

推送和通知實際上是兩種不同的概念和技術,它們結合起來就是我們所知的 推送通知。推送提供了允許伺服器向 service worker 傳送訊息的機制,通知就是 servic worker 向使用者顯示資訊的方式。

因為 service workers 即使在 app 沒有執行的時候也可以執行,它們可以監聽即將到來的推送事件。然後它們要麼提供使用者通知,要麼更新 app 狀態。

推送事件用後端通過瀏覽器推送服務啟動,如 Firebase 提供的推送服務。

下面這個例子展示了 web worker 如何能夠監聽到即將到來的推送事件:

self.addEventListener('push', (event) => { 
  console.log('Received a push event', event) 
  const options = { 
    title: 'I got a message for you!', 
    body: 'Here is the body of the message', 
    icon: '/img/icon-192x192.png', 
    tag: 'tag-for-this-notification', 
  } 
  event.waitUntil( 
    self.registration.showNotification(title, options) 
  ) 
})
複製程式碼

有關控制檯日誌的說明:

如果 service work 有任何控制檯日誌語句(console.log 和其類似),請確保你開啟了 Chrome Devtools(或類似工具)提供的 Preserve log 功能。

否則,由於 service worker 在頁面載入前執行,並且在載入頁面前清除了控制檯,你將不會在控制檯看到任何日誌輸出。

感謝閱讀這篇文章,關於這個主題還有很多值得學習的地方!我在關於前端開發的部落格中發表了很多相關的內容,別忘記去看!?

最初發表於flaviocopes.com


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章