使用Service worker實現加速/離線訪問靜態blog網站

發表於2017-02-19

640?wx_fmt=gif


作者:Google 開發技術專家 (GDE) 楊波 (Alpha)


現在很流行基於 GitHub page 和 markdown 的靜態 blog,非常適合技術的思維和習慣,針對不同的語言都有一些優秀的靜態 blog 系統出現,如 Jekyll/Ruby,Pelican/Python,Hexo/NodeJs,由於靜態內容的特性非常適合做快取來加速頁面的訪問,就利用 Service worker 來實現加速,結果是除了 PageSpeed,CDN 這些常見的伺服器和網路加速之外,通過客戶端實現了更好的訪問體驗。



加速/離線訪問只需三步

1. 首頁新增註冊程式碼

640?wx_fmt=png

   

2. 複製程式碼

將 https://alphayang.github.io/sw.js 儲存到你的網站根目錄下。


3. 修改不快取域名列表及離線狀態頁面

在你的 sw.js 中修改

640?wx_fmt=png


開啟 Chrome Dev Tools->Source,看看自己的 blog 都引用了哪些第三方資源,逐個加到忽略列表裡。

640?wx_fmt=png


在根目錄下新增 offline.html,在沒有網路且快取中也沒有時使用,效果如下:

640?wx_fmt=png


在根目錄下新增 offline.svg,在無網路時圖片資源請求返回該檔案。


4. 加速效果

首頁加速後,網路請求從 16 降為 1,載入時間從 2.296s 降為 0.654s,得到了瞬間載入的結果。

640?wx_fmt=png



加速/離線原理探索

什麼是 Service worker?

640?wx_fmt=png


如上圖,Service worker 是一種由 Javascript 編寫的瀏覽器端代理指令碼,位於你的瀏覽器和伺服器之間。當一個頁面註冊了一個 Service worker,它就可以註冊一系列事件處理器來響應如網路請求和訊息推送這些事件。Service worker 可以被用來管理快取,當響應一個網路請求時可以配置為返回快取還是從網路獲取。由於 Service worker 是基於事件的,所以它只在處理這些事件的時候被調入記憶體,不用擔心常駐記憶體佔用資源導致系統變慢。


Service worker 生命週期

640?wx_fmt=png


Service worker 為網頁新增一個類似於 App 的生命週期,它只會響應系統事件,就算瀏覽器關閉時作業系統也可以喚醒 Service worker,這點非常重要,讓 Web App與 Native App 的能力變得類似了。


Service worker 在 Register 時會觸發 Install 事件,在 Install 時可以用來預先獲取和快取應用所需的資源並設定每個檔案的快取策略。


一旦 Service worker 處於 activated 狀態,就可以完全控制應用的資源,對網路請求進行檢查,修改網路請求,從網路上獲取並返回內容或是返回由已安裝的 Service worker 預告獲取並快取好的資源,甚至還可以生成內容並返回給網路語法。


所有的這些都使用者都是透明的,事實上,一個設計優秀的 Service worker 就像一個智慧快取系統,加強了網路和快取功能,選擇最優方式來響應網路請求,讓應用更加穩定的執行,就算沒有網路也沒關係,因為你可以完全控制網路響應。


Service worker 的控制從第二次頁面訪問開始

在首次載入頁面時,所有資源都是從網路載的,Service worker 在首次載入時不會獲取控制網路響應,它只會在後續訪問頁面時起作用。


640?wx_fmt=png


頁面首次載入時完成 install,並進入 idle 狀態。


640?wx_fmt=png


頁面第二次載入時,進入 activated 狀態,準備處理所有的事件,同時 瀏覽器會向伺服器傳送一個非同步 請求來檢查 Service worker 本身是否有新的版本,構成了 Service worker 的更新機制。


640?wx_fmt=png


當 Service worker 處理完所有的事件後,進入 idle 狀態,最終進入 terminated 狀態資源被釋放,當有新的事件發生時再度被呼叫。


特點

  • 瀏覽器: Google Chrome,Firefox,Opera 以及國內的各種雙核瀏覽器都支援,但是 safari 不支援,那麼在不支援的瀏覽器裡 Service worker 不工作。

  • https: 網站必須啟用 https 來保證使用 Service worker 頁面的安全性,開發時 localhost 預設認為是安全的。

  • non-block: Service worker 中的 Javascript 程式碼必須是非阻塞的,因為 localStorage 是阻塞性,所以不應該在 Service Worker 程式碼中使用 localStorage。

  • 單獨的執行環境: Service worker 執行在自己的全域性環境中,通常也執行在自己單獨的執行緒中。

  • 沒有繫結到特定頁面: Service worker 能控制它所載入的整個範圍內的資源。

  • 不能操作 DOM: 跟 DOM 所處的環境是相互隔離的。


    640?wx_fmt=png

  • 沒有瀏覽頁面時也可以執行: 接收系統事件,後臺執行。

  • 事件驅動,需要時執行,不需要時就終止: 按需執行,只在需要時載入到記憶體。

  • 可升級: 執行時會非同步獲取最新的版本。


實現加速/離線

Cache

網頁快取有很多,如 HTTP 快取,localStorage,sessionStorage 和 cacheStorage 都可以靈活搭配進行快取,但操作太繁瑣,直接使用更高階 Service worker –本文的主人公。


新增 Service worker 入口

在 Web App 的首頁新增以下程式碼

640?wx_fmt=png


如果瀏覽器支援 Service worker 就註冊它,不支援還是正常瀏覽,沒有 Service worker 所提供的增強功能。


Service worker 控制範圍:

簡單情況下,將 sw.js 放在網站的根目錄下,這樣 Service worker 可以控制網站所有的頁面,同理,如果把 sw.js 放在 /my-app/sw.js 那麼它只能控制 my-app 目錄下的頁面。


把 sw.js 放在 /js/ 目錄呢?更好的目錄結構和範圍控制呢?在註冊時指定 js 位置並設定範圍。

navigator.serviceWorker.register('/js/sw.js', {scope: '/sw-test/'}).then(function(registration) {

      // Registration was successful

      console.log('ServiceWorker registration successful with scope: ', registration.scope);

    }).catch(function(err) {

      // registration failed :(

      console.log('ServiceWorker registration failed: ', err);

    });


Service worker 實現

監聽三個事件:

640?wx_fmt=png


install

640?wx_fmt=png

   

install 時將所有符合快取策略的資源進行快取。


fetch

640?wx_fmt=png


onFetch 做為瀏覽器網路請求的代理,根據需要返回網路或快取內容,如果獲取了網路內容,返回網路請求時同時進行快取操作。

   

activate

///////////

// Activate

///////////

function onActivate(event) {

  log('activate event in progress.');

  event.waitUntil(removeOldCache());

}

function removeOldCache() {

  return caches

    .keys()

    .then((keys) => {

      return Promise.all( // We return a promise that settles when all outdated caches are deleted.

        keys

         .filter((key) => {

           return !key.startsWith(version); // Filter by keys that don't start with the latest version prefix.

         })

         .map((key) => {

           return caches.delete(key); // Return a promise that's fulfilled when each outdated cache is deleted.

         })

      );

    })

    .then(() => {

      log('removeOldCache completed.');

    });

}

 

在 activate 時根據 version 值來刪除過期的快取。


管理 Service worker

特定網站

1) Google Chrome

Developer Tools->Application->Service Workers,

640?wx_fmt=png


在這裡還有三個非常有用的核取方塊:

  • Offline: 模擬斷網狀態

  • Update on reload: 載入時更新

  • Bypass for network: 總是使用網路內容


2) Firefox

只有在 Settings 裡有一個可以在 HTTP 環境中使用 Service worker 的選項,適應於除錯,沒有單獨網站下的 Service worker 管理。

640?wx_fmt=png


3) Opera 及其它雙核瀏覽器同 Google Chrome

如果看到多個相同範圍內的多個 Service worker,說明 Service woker 更新後,而原有 Service worker 還沒有被 terminated。


瀏覽器全域性

看看你的瀏覽器裡都有哪些 Service worker 已經存在了。


1) Google Chrome

在位址列裡輸入:

chrome://serviceworker-internals/


可以看到已經有 24 個 Service worker 了,在這裡可以手動 Start 讓它工作,也可以 Unregister 解除安裝掉。

640?wx_fmt=png


2) Firefox

有兩種方式進入 Service worker 管理介面來手動 Start 或 unregister。

  • 選單欄,Tool->Web Developer->Service workers

  • 位址列中輸入: 

    about:debugging#workers

640?wx_fmt=png


3) Opera 及其它雙核瀏覽器同 Google Chrome


更多

TODO:

  • Service workers 的更新需要手動編輯 version,每次釋出新文章時需要編輯;

  • 使用 AMP 讓頁面渲染速度達到最高。


作者其他文章:

AR/VR/MR,Android開發者可以做些什麼?

Android無處不在,Android開發者大有可為

與谷歌開發技術專家一起,開啟“I/O地圖開發技術”之旅


640?wx_fmt=gif

相關文章