iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
3
Modern Web

30天走訪Progressive Web Apps(PWAs)系列 第 7

Day7-Service Worker 走訪Lifecyle(程式篇)

  • 分享至 

  • twitterImage
  •  

昨日我們過目Service Worker的生命週期後,今天第一步,在專案/public,底下建立一個service-worker的js檔案。
https://ithelp.ithome.com.tw/upload/images/20171221/20103808rIwD2Hp4gh.png
檔案名稱可以隨自己開心沒有規定,接著,我們在/src/js資療夾底下,建立app.js,提供index.html中使用的JS檔案。

註冊 Service Worker

if('serviceWorker' in navigator){
    navigator.serviceWorker
        .register('/service-worker.js')
        .then(function(){
            console.log('Service Worker 註冊成功');
        }).catch(function(error) {
            console.log('Service worker 註冊失敗:', error);
        });
} else {
  console.log('瀏覽器不支援');
}

接著打開app.js輸入上面這段程式,就完成註冊了,一整個很簡單/images/emoticon/emoticon01.gif
解讀起來就是詢問瀏覽器(navigator)中是否存在(in)名為「serviceWorker」的物件存在,如果瀏覽器支援Service Worker,就會進入「navigator.serviceWorker.register(檔案路徑)」,檔案路徑「/檔案名稱」,/是確保路徑都是從root開始看,從專案的架構,就是指/public/底下的範圍。

關於路徑,有一個重點

service worker的檔案放置的位置會影響,能掌管的範圍。
舉例來說,預設的掌管範圍放在根目錄底下的service worker
可以存取到的範圍為整個網站:

  • websiteDemo.com/Index.html (○)
  • websiteDemo.com/Product/Detail.html (○)

如果檔案存放在「/public/Product/service-worker.js」,
能控制的範圍為Produc資料夾範圍內的頁面:

  • websiteDemo.com/Index.html (X)
  • websiteDemo.com/Product/Detail.html (○)

透過scope參數,可以加限制存取範圍

    //假設這行是在「/Product/Detail.html」頁面執行
    navigator.serviceWorker.register('/sw.js', {scope: './'})

「./」依照註冊Service Worker的頁面索在資料夾為基準,意思就是只能存取「/Product/」底下的所有資源。

Service Worker回傳會promise,關於promise會再後面幾篇中再做介紹。

考量

在網頁載入的時候,先不考慮Service Worker對於開發者最注重的就是畫面資料的顯示速度,而今天就算我們網站有使用Service Worker,在使用者感受上,他並不會有任何感受,因為都是運行在背景上。

而假如今天我們網頁有很多圖片、CSS和JS資源要載入,是必我們就必須分配CPU和記憶體來執行,雖然現在手機規格越來越好,但我們還是必須假設頻寬是有一定限制,既然Service Worker對使用者感受上不會有任何反應,我們等頁面資源全部載入後再註冊也不遲。

因此,就將上面的註冊方法改寫了一下,讓資源等待網頁載入後,再註冊Service Worker

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/service-worker.js');
  });
}

但是否該同時載入,還是依照情境再做決定,並沒有強制說一定要使用哪一個。

終於打開 service-worker.js啦

install事件

app.js註冊Service Worker後,可以透過install事件,來捕捉網頁上有沒有成功註冊Service Worker

  • 每個Service Worker只會註冊一次,當更新了Service Worker的檔案後,重新瀏覽頁面會被視為新的Service Worker
self.addEventListener('install', function(event){
    console.log('[SW] 安裝(Install) Service Worker!',event);
});

self是一個語法糖,讓我們可以存取Service Worker的背景程式,有寫過網頁的應該都覺得addEventListener這個關鍵字很熟悉,但是在Service Worker裡面,是無法使用click之類我們平常所使用的事件,因為再前一天說過,Service Worker是一套運行再背景的程式,是沒有權限能存取DOM的,所以理所當然能操作DOM的事件在這邊都是無法使用的。

https://ithelp.ithome.com.tw/upload/images/20171223/20103808A8AUdgPN0A.jpg
接著我們運行網站後,開F12/Console視窗,可以看到成功監聽到註冊的訊息,代表成功抓到Service Worker的檔案了。

activate 事件

再安裝完之後,Service Worker就可以執行功能性的需求(如:推播)。

self.addEventListener('activate', function(event){
    console.log('[SW] 觸發(Activate) Service Worker!',event);
    return self.clients.claim();
});

接著新增監聽activateclients.claim(),是一個控制住,不受控的資源的方法,看官網用了一個Demo介紹了這個方法。
它的意思是,當你打開頁面的時候一開始你會註冊Service Worker,但是你並還沒有接收到圖檔的需求,因為設定3秒的Timeout才發出dog.svg的需求,所以需求並沒有經過Service Worker,這時候你再f5刷新畫面一次,觸發Fetch事件的時候,頁面和圖檔就經過Service Worker的時候,原本是狗的圖就被替換成貓了。

事件後執行,會發現console視窗,只出現註冊及安裝的訊息。

Service Worker 註冊成功
[SW] 安裝(Install) Service Worker! InstallEvent {isTrusted: true, type: "install", target: ServiceWorkerGlobalScope, currentTarget: ServiceWorkerGlobalScope, eventPhase: 2, …}

那是因為假如我們瀏覽器上已經有一個Service Worker已經註冊了,現在我們更新了新版的Service Worker上去後,他會等待舊的Service Worker終止後,才執行新的Service Worker,為了方便Debug,Chrome在開發者工具(F12)上,有一個Application區塊,有Service Workers的狀態區,讓我們可以清楚現在網頁上的狀況。

https://ithelp.ithome.com.tw/upload/images/20171223/20103808zRDyV9YLrs.jpg
如上圖,綠色#68401的點是我們第一次在測試install事件時註冊的Service Worker
而橘色#68402是在加了activate事件後的Service Worker

那你會說這樣不就每次Debug都要先清掉前面註冊的不是很麻煩嗎?

Application/Service Workers上,有三個方式可以解決這個問題。

  1. 關掉網站的分頁重新載入。
  2. 點選橘色點(新的Service Worker),最右邊**skipWaiting**的連結,它可以跳過等待時間直接強制執行新的Service Worker
  3. 將上面**Update on reload**打勾,意思是每次重整時,都重新註冊Service Worker
    https://ithelp.ithome.com.tw/upload/images/20171223/20103808vDeo02fKsB.jpg

關於解法第一點小提醒

如果在沒有使用第2-3點的情境下,你修改了新的Service Worker,當前一次的localhost還掛在頁面上,再建置後,開啟新的localhost頁面時候,會發現舊的Service Worker一直掛在上面,那是因為有舊的分頁沒關掉造成的,如果要更新就要將全部localhost的分頁關掉再進入網站,才會載入新的Service Worker

總結

關於Service Worker

  • install 事件是第一個會觸發的事件,而且再頁面上只會觸發一次。
  • 預設情況下,Fetch不會通過Service Worker,除非Service Worker已經install,且再一次刷新頁面才會有效果。
  • 可以透過clients.claim()強制控制沒有控制到的資源。

關於 Service Worker更新時間

  • 第一次進到有註冊Service Worker的頁面的時候。
  • Service Worker的程式有改變的時候,他會觸發install事件,並進入activate中等待,可以透過skipWaiting強制更新。
  • 如果新的Service Worker再執行install的時候失敗,會被刪除,舊的Service Worker仍然會繼續執行。
  • 新的Service Worker安裝後,會處於等待狀態(wait),當舊的Service Worker沒有控制任何資源的時候,才會刪除舊的觸發新的。

閱讀資源

MDN:SW.register()
Clients.claim()
The Service Worker Lifecycle

GitHub

https://github.com/DakHarry/30day-pwas-practice


上一篇
Day6- Service Worker終結小恐龍的命運(觀念篇)
下一篇
Day8-Fetch API與Promise 使用方式介紹
系列文
30天走訪Progressive Web Apps(PWAs)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言