Progressive Web Applications

Progressive Web Applications take advantage of new technologies to bring the best of mobile sites and native applications to users. They're reliable, fast, and engaging.html

基礎知識

用戶首次訪問service worker控制的網站或頁面時,service worker會馬上被下載。
ServiceWorker(web worker 的一種)接口
Cache:表示對request/response對象的存儲,一個域能夠有多個 Cache 對象. 你將在你的代碼中處理和更新緩存 . 在 Cache 除非顯示地更新緩存, 不然緩存將不會被更新; 緩存數據不會過時, 除非刪除它
Cache.match(request, options)返回一個Promise,查找cache中匹配的request
Cache.match(request, options)匹配一個數組對象中的request
Cache.add(request)發送請求並將請求放入cache中,
Cache.put(request, response)將request和response都添加到cache中
Cache.delete(request, options) 才cache中查找鄉音的值,並刪除返回一個promise,resoleve爲true,若是找不到返回false
Cache,keys(request, options)返回一個promise,resolve爲全部的cache鍵值vue

CacheStorage: 對Cache對象的存儲,提供命名緩存的主目錄,sw能夠經過訪問並維護名字字符串到Cache對象的映射
caches.open(cacheName).then(names){};//打開一個cache對象ios

Client: 表示sw client的做用域。git

sw.js中的self:這個關鍵字表示的是一個service worker 的執行上下文的一個全局屬性(ServiceWorkerGlobalScope),相似於window對象,不過這個self是做用於service worker的全局做用域中。github

sw生命週期

image.png

覆蓋率

image.png

注意點

  1. 基於https
    可使用http-server+ngrok配合,固然更簡單的使用github。
    2.Service worker是一個註冊在指定源和路徑下的事件驅動worker。實際上 SW 在你網頁加載完成一樣也能捕獲已經發出的請求,因此,爲了減小性能損耗,咱們通常直接在 onload 事件裏面註冊 SW 便可。
  2. 做用域問題
    SW 的做用域不一樣,監聽的 fetch 請求也是不同的。 例如,咱們將註冊路由換成: /example/sw.js,那麼,SW 後面只會監聽 /example 路由下的全部 fetch 請求,而不會去監聽其餘

register

if(navigator.serviceWorker){
    navigator.serviceWorder.register('sw.js')
        .then(registration  =>  {
              console.log(`registered event at scope:${registration.scope}`);
        })
        .cache(err => {
               throw err;
         })
}

install

self.addEventListener('install', function(event) {
  // Perform install steps
});

緩存文件web

const cacheVersion = 'v1';
const cacheList = [
    '/',
    'index.html',
    'logo.png',
    'manifest.json',
    '/dist/build.js'
];
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheVersion).then(function(cache) {
      return cache.addAll([cacheList]);
    })
  );
});

event.waitUntil()參數必須爲promise,它能夠延長一個事件的做用時間,由於咱們在打開緩存或者更新的時候頗有可能會有延遲,而event.waitUntil()能夠防止事件終端。另外它會監聽全部的異步promise,一旦有一個reject那麼該次event即是失敗的,也就是說sw啓動失敗。固然若是有些文件比較大很差緩存的話別讓它返回就行了:json

cache.addAll([cachelist1]);
return cache.addAll([cachelist2]);

fetchEvent

緩存捕獲,當發起請求的時候將request和response緩存下來(緩存一開始定義的緩存列表)。axios

self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                if(response){
                    return reponse;
                 }
                  return fetch(event.request);
             })
    )
})

這是個比較簡單的格式,event.respondWith(r),包含請求響應代碼,能夠設置一個參數r,r是一個promise,resolve以後是一個response對象。整段代碼意思就是當請求一個文件時,若是緩存中已經有了,那麼就直接返回緩存結果,不然發起請求。數組

問題:若是沒有緩存咱們怎麼處理?
  1. 等下次sw根據路由去緩存;
  2. 手動緩存promise

    手動緩存
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                if(response){
                    return response;
                }
                //fetch請求的request、response都定義爲stream對象,因此只能讀一次這裏須要clone一個新的
                let requestObj = event.request.clone();

                return fetch(requestObj)
                            .then(response => {
                                //檢測是否成功
                                if(!response || response.status !== 200 || response.type !== 'basic') {
                                    return response;
                                }
                                //若是請求成功,第一要去渲染,第二要緩存
                                //cache.put()也使用stream,因此這裏也須要複製一份
                                let responseObj = response.clone();

                                caches.open(cacheVersion)
                                    .then(cache => {
                                        cache.put(event.request, responseObj);
                                    });
                                return response;

                            })
            })
    )
})
爲何stream只能讀一次?

當可讀流讀取一次以後可能已經讀到stream結尾或者stream已經close了,這裏request和response都實現了clone接口來複制一份,因此在須要二次使用stream的時候就須要用副原本實現了。

刪除舊的緩存

self.addEventListener('activate', evnet => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.filter(cachename => {
                    if(cachename == cacheVersion){
                        return caches.delete(cachename);
                    }
                })
            ).then(() => {
                return self.clients.claim()
            })
        })
    )
})

咱們檢查以前保存的sw緩存,還要注意一點就是Promise.all()中不能有undefined,因此咱們對於相同的版本要過濾,於是不使用map,避免返回undefined。
經過調用 self.clients.claim() 取得頁面的控制權, 這樣以後打開頁面都會使用版本更新的緩存。

更新

當你更新了你的sw文件,並修改了cacheVersion以後,刷新瀏覽器,期待的變化並無發生,由於雖然你改變了緩存版本,可是此時舊的sw還在控制整個應用,新的sw並無生效。這時就須要更新一下sw,有如下方法

  1. registration.update() ,也就是在註冊的時候選擇合適方式更新
navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
    reg.update();
});
  1. 使用self.skipWaiting();
    在install階段使用這個可使得新的sw當即生效。
self.addEventListener('install', event => {
      self.skipWaiting();

      event.waitUntil(
        // caching
      );
});
  1. 調試手動更新
    image.png

直接點擊update便可。
注意,咱們更新了某些文件的時候也要同時更新sw中的緩存版本(cacheVersion)

manifest文件

這個文件主要是配置添加到桌面的一些基本信息,好比圖標啓動頁等。詳細能夠看這個https://developer.mozilla.org/zh-CN/docs/Web/Manifest

下面是我寫的一個示例https://github.com/Stevenzwzhai/vue2.0-elementUI-axios-vueRouter/blob/master/pwa/sw.js

或者拉取這個項目https://github.com/Stevenzwzhai/PWA-demo

最近寫了個知乎日報的pwa,有興趣能夠看下一篇文章

項目地址https://github.com/Stevenzwzhai/zhihu-daily
演示地址https://stevenzwzhai.github.io/zhihu-daily/

相關文章
相關標籤/搜索