Service worker

可以拿來做cache 的另一個方式

在開啟網頁時,需要先確認該瀏覽器是否支持serviceWorker,如果支持,就可以依照 js 內容來使用serviceWorker。

register 時需要注意的是,serviceWorker 只能cache 自己目錄 或者 下層目錄 的內容,scope 可以在此時指定。

在此範例內的swFinal.js 有帶著版本號,這樣才能在更新檔案後立刻反應

test.html 內容

<!DOCTYPE html>
<html lang="en">
   <head>
      <link rel="manifest" href="manifest.json" crossorigin="use-credentials">
      <script type="text/javascript">
         if ( "serviceWorker" in navigator ) {
            //1. 路徑:這個js路徑會影響能存取的範圍(該目錄&下層目錄),放根目錄為整站
            //2. scope:放置js 檔案的路徑下的某個路徑才會cache.
            // 預設 : navigator.serviceWorker.register( "swFinal.js" )
            // 加上scope: navigator.serviceWorker.register( "swFinal.js",{scope: './'} ) //相當於沒寫
            // 加上scope: navigator.serviceWorker.register( "swFinal.js",{scope: '/aaa/'} ) //放置js 檔案的目錄下的aaa資料夾下檔案
            navigator.serviceWorker.register( "swFinal.js?version=1.02" )     //改版號就可以換sw,避免使用到原本sw 裡面檔案還是舊的
             .then( function ( registration ) { 
               console.log( "ServiceWorker registration successful with scope: ", registration.scope );
               const sw = registration.installing || registration.waiting || registration.active;
               sw.postMessage({ milliseconds: Date.now() });
             } ).catch( function ( err ) { 
               console.error( "ServiceWorker registration failed: ", err );
             } );
         
         }
      </script>
   </head>
   <body>
      My awesome PWA
   </body>
</html>

swFinal.js 是要register 使用的檔案,內容包含了

  1. activate:啟動時,會自動呼叫的event,這邊會將所有cached 的文件做一次檢查,並剔除舊的檔案(非目前版本)
  2. install :在activate 之後會自動呼叫的event,我們在這邊add 所有需要cached 的檔案,另外也添加一個OFFLINE時使用的檔案
  3. fetch:取檔案用,如果cache內有該檔案,則直接取出使用,沒有在列表內也可以設定DYNAMIC_CACHE_LIMIT 來啟動動態cache 機制。當網頁伺服器壞掉,也會返回原本cached 的OFFLINE 頁面。

swFinal.js 內容

const VERSION = "1.02";
const DYNAMIC_CACHE_LIMIT = 0;
const CACHE_DYNAMIC_NAME = "dynamic-" + VERSION;
const CACHE_STATIC_NAME = "static-" + VERSION;
const STATIC_FILES = [
        '/',
        '/index.html?version=1.01',
        '/testCache2.html?version=1.01',
        'https://fonts.googleapis.com/css?family=Noto+Sans+TC&display=swap',
        'https://fonts.googleapis.com/icon?family=Material+Icons',
        'https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.indigo-pink.min.css'
    ];
const OFFLINE_URL  = "/offline.html";                                                 //這個是讓有跑過此service worker 預載後,網路斷了又要載入不在列表內的檔案時,預設載入此檔案

self.addEventListener('message', function(event) {
     console.log('[Service Worker] Message Service Worker ...', event);
});

self.addEventListener('activate', function(event) {
    console.log('[Service Worker] Activating Service Worker ...', event);
    event.waitUntil(
        caches.keys().then(function(keyList) {
            return Promise.all(keyList.map(function(key) {
                if(key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {       //遍尋所有cache 刪除非目前版本的
                    console.log('[Service Worker] Removing old cache.', key);
                    return caches.delete(key);
                }
            }));
        })
    );
    return self.clients.claim();
});
/*  Service Worker Event Handlers */
self.addEventListener('install', function(event) {
    console.log('[Service Worker] Installing Service Worker ...', event);
    event.waitUntil(
        caches.open(CACHE_STATIC_NAME).then(function(cache) {
            console.log('[Service Worker] Precaching App Shell');
            cache.addAll(STATIC_FILES);                                             //這邊預載列表內的所有檔案到cache.
            cache.add(OFFLINE_URL);                                                 //這邊預載列表內的所有檔案到cache.
        })
    );
    self.skipWaiting();                                                             //假設原本有另一個分頁開啟同一個sw,會直接蓋掉,而不是預設的waiting.
});

self.addEventListener('fetch', function(event) {
    // Cache with Network Fallback
    event.respondWith(
        caches.match(event.request).then(function(response) {
            if(response) {                                                          //在cache 內找到
                return response;                                                    //回傳cache 內容
            } else {                                                                //cache 內沒有,用fetch去抓並存起來
                return fetch(event.request).then(function(res) {
                    return caches.open(CACHE_DYNAMIC_NAME).then(function(cache) {   //動態的,存到動態的cache 分類中
                        if(DYNAMIC_CACHE_LIMIT > 0){
                            trimCache(CACHE_DYNAMIC_NAME, DYNAMIC_CACHE_LIMIT);         //動態cache 的部分,加上數量限制
                            cache.put(event.request.url, res.clone());                  //加入動態cache
                        }
                        return res;
                    })
                }).catch(function(err) {
                    return caches.open(CACHE_STATIC_NAME).then(function(cache) {    //fetch失敗(沒網路)
                        if(event.request.headers.get('accept').includes('text/html')) {
                            return cache.match(OFFLINE_URL);                        //返回預設的斷線畫面
                        }
                    });
                });
            }
        })
    );
});

function trimCache(cacheName, maxItems) {
    caches.open(cacheName).then(function(cache) {
        return cache.keys().then(function(keys) {
            if(keys.length > maxItems) {
                cache.delete(keys[0]).then(trimCache(cacheName, maxItems));
            }
        });
    })
}

參考資料:

https://ithelp.ithome.com.tw/articles/10220322

發佈留言