[譯] Service workers:PWA背後的英雄

原文地址:medium.freecodecamp.org/service-wor…
做者:Flavio Copes
摘要:這篇文章簡述service worker做爲PWA核心技術如何實現資源緩存和消息推送的功能,還幫助讀者理解service worker的生命週期。git

Service worker是漸進式網絡應用(Progressive Web Apps)的核心。它們幫助咱們實現本來是原生app纔有資源緩存和消息推送兩大特性。github

Service worker是你的網頁與網絡間的代理,它可以攔截和緩存來往的網絡請求。這能夠幫助你的應用創造一個離線環境下也能良好訪問的用戶體驗。web

首先介紹一下web worker的概念。它是一個與指定網頁相關聯的JS文件,獨立與主線程運行在一個特定的上下文環境中,這樣就不會爲了計算數據去犧牲UI的性能,從而避免了阻塞的狀況。而service worker是一種特殊的web worker。chrome

而正是因爲它是一個子線程,因此沒法操做DOM。一樣也沒法訪問Local Storage API和XHR API。它只能經過Channel Messaging API和主線程通訊。shell

Service Worker可以與下面幾個API合做:後端

  • Promises
  • Fetch API
  • Cache API

只有在HTTPS協議下的網頁裏它們纔會起做用。(不過不包括本地的網絡請求,由於它們不須要保持安全鏈接。這樣也方便咱們調試。)promise

後臺進程

Service worker可以獨立於與它關聯的應用程序運行,而且在這些程序處於非活躍狀態下仍能夠接收消息。瀏覽器

讓我來舉幾個場景:緩存

  • app處於後臺非活躍狀態下運行;
  • app被關閉;
  • 呈現你網頁的瀏覽器被關閉;

那麼service worker將不受影響地繼續工做。安全

Service worker的有用之處在於:

  • 它們能夠看成緩衝層,處理網絡請求和緩存離線所需的資源;
  • 它們能夠用來推送消息。

Service worker只在須要的時候運行,其餘狀況下都會中止工做。

支持離線

對於傳統網頁,離線狀況下的用戶體驗很是糟糕。若是用戶沒有聯網,移動端的web應用通常是直接中止工做。反觀原生應用,會展現給用戶一些友好的提示信息。

下面這張圖是Chrome瀏覽器中離線網頁顯示的內容,顯然這並不算是一個友好的提示信息:

chrome離線

也許惟一不錯的地方是你能夠經過點擊恐龍來免費玩一個遊戲,不過相信你很快就會變得不耐煩了。

無聊的遊戲

不久以前,HTML5標準下的AppCache可讓web應用緩存離線資源,可是它缺少靈活性而且有一些使人困惑的行爲,這也說明它沒法勝任支持離線這項工做。

而如今,service worker成了離線緩存的新標準。

那麼,它實現哪些緩存呢?

安裝時的預緩存

像圖片、CSS文件和JS文件都會在app的使用過程當中重複用到。這些資源能夠在app打開的第一時間緩存好。

這也是所謂的APP殼架構( App Shell architecture)的基礎。

緩存網絡請求

咱們使用Fetch API能夠對服務器返回的響應報文進行編輯,根據服務器是否可達來決定是否使用緩存中的響應報文代替。

生命週期

一個service worker在啓動前經歷了三步:

  • 註冊(Registration)
  • 安裝(Installation)
  • 激活(Activation)

註冊

註冊階段是通知瀏覽器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以前沒有註冊過或是須要更新的狀況下進入註冊階段。

Scope

register函數須要一個scope參數來指明你的web應用被該service worker管理的文件所在路徑。

這個參數的默認值是全部文件以及service worker文件父級目錄下的全部子文件夾。因此若是你把service worker文件放在根目錄下,它會管理整個web應用。而若是在某個子文件夾中,它只會管理該路徑可以訪問的網頁。

下面這個例子經過指定scope參數爲/notifications/目錄來註冊一個service worker。

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

結尾的/很是重要,能夠避免/notification頁面觸發service worker。而若是寫成下面這樣:

{ scope: '/notifications' }
複製代碼

那麼service worker就將一樣做用於/notification頁面。

注意:service worker沒法控制自身所在目錄之外的文件。也就是說,若是service worker文件被放在/notification文件夾下,它沒法控制根目錄/或其餘不屬於/notification的文件。

安裝

若是瀏覽器發現一個service worker過時或以前沒有註冊過,那麼它將安裝這個service worker。

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

這是使用service worker初始化緩存,而後利用Cache API來緩存APP shell和靜態資源的好時機。

激活

一旦service worker註冊並安裝成功後 ,咱們來到了第三階段:激活。

這時,service worker可以在加載新頁面時開始工做。

它不能做用於激活前已經加載過的頁面,因此只有重啓app或是刷新已加載頁面兩種方式來使它工做。

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

監聽這個事件能夠用來清除舊緩存或者是刪除新版service worker不須要的舊資源。

更新

你僅僅是修改一字節的文件就須要更新一次service worker。它會在註冊的代碼再次執行時被更新。

更新後的service worker只有在全部頁面都被關閉後纔開始代替以前的service worker工做。若是僅僅是刷新頁面是不會起做用的,由於以前的service worker仍然在運行且沒有被刪除。

這種機制保證了更新不會讓以前在運行的app或網頁崩潰。

Fetch事件

當瀏覽器發送網絡請求時就會觸發Fetch事件。

咱們藉此能夠在請求發送時檢查緩存中是否已經存儲所需資源。

舉個例子,下面的代碼使用了Cache API來檢查請求的URL是否已經被緩存。若是是,那麼返回緩存中的響應數據,不然會發送請求而後返回響應數據。

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

Background Sync

當用戶在離線狀態下發送網絡請求時,Background Sync這個API將延遲該請求直到用戶脫離離線狀態。

這保證了用戶在離線狀態下仍然可使用並操做app,這些離線操做會保存在隊列中,以便在鏈接網絡後向服務端發出響應請求。(是否是比展現一個無休止的loading圖標要好多了?)

navigator.serviceWorker.ready.then((swRegistration) => { 
    //註冊一個事件event1
  return swRegistration.sync.register('event1') 
});
複製代碼

下面的代碼是在service worker中監聽這個事件:

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

doSomething()返回一個promise。若是返回的promise被拒,另外一個同步事件被自動地開始重試操做,直到返回一個成功狀態的promise。

這也使得app可以在聯網時馬上更新服務器發來的數據。

消息推送

Service worker使得web應用能夠像原生同樣推送消息給用戶。

事實上,推送和消息通知是兩個不一樣的概念,它們組合而成的技術纔是咱們熟悉的消息推送。推送機制使得服務器可以向service worker發送信息,而後service worker將信息展現給用戶纔是消息通知。

因爲service worker能夠在app關閉後繼續運行,因此它們可以一直監聽推送事件。而後它們能夠發送消息通知,或者是更新app的狀態。

推送事件會在後端經過瀏覽器推送服務後啓動,好比說Firebase的推送服務。

下面的代碼演示了web worker如何監聽push事件:

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) 
  ) 
})
複製代碼

關於console.log

若是你的代碼中包含console.log或是其餘相似的控制檯輸出語句,務必打開Chrome開發工具中的Preserve log功能。

不然的話,由於service worker在網頁加載前就開始執行,而此時控制檯會被清空,因此你沒法看到任何日誌輸出。

做者總結

很是感謝閱讀本篇教程,事實上,關於PWA還有不少要學習的知識。若是您有什麼看法歡迎在下面評論。

譯者注:主流瀏覽器開始逐漸支持service worker,之後PWA是否會真的與原平生分秋色呢?將來如何,我想如今多瞭解一點PWA的知識總不會壞事。
項目地址:github.com/WhiteYin/tr…

相關文章
相關標籤/搜索