下一代 Web 應用應用模型Progressive Web App(PWA)- Service Worker

背景

Web 應用的現狀html

  1. 網絡資源下載帶來的網絡延遲
  2. Web 應用依賴於瀏覽器做爲入口
  3. 沒有很好的離線使用方案
  4. 沒有好的消息通知方案
  5. ….

針對以上問題,結局方案出現了PWA前端

PWA簡介

PWA全稱Progressive Web App,即漸進式WEB應用。 一個 PWA 應用首先是一個網頁, 能夠經過 Web 技術編寫出一個網頁應用. 隨後添加上 App Manifest 和 Service Worker 來實現 PWA 的安裝和離線等功能web

PWA主要特色

  • 可靠-即便在不穩定的網絡環境下,也能瞬間加載並展示
  • 體驗-快速響應,而且有平滑的動畫響應用戶的操做
  • 粘性-像設備上的原生應用,具備沉浸式的用戶體驗,用戶能夠添加到桌面

PWA特色的實現

  • 可靠-離線緩存- Service Worker
  • 體驗-web 存儲- App Shell 模型
  • 粘性-吸引留住用戶(添加到主屏幕和網絡推送通知)- manifest.json

Service Worker

Service Worker 是瀏覽器在後臺獨立於網頁運行的腳本,它打開了通向不須要網頁或用戶交互的功能的大門。 Service Worker從英文翻譯過來就是一個服務工人,服務於前端頁面的後臺線程,基於Web Worker實現。有着獨立的js運行環境,分擔、協助前端頁面完成前端開發者分配的須要在後臺悄悄執行的任務。編程

客戶端訪問,經過Service Worker 服務,判斷請求內容從哪裏取的,若是緩存中存在,直接 取緩存,不然就走網絡

Service Worker功能

  • 推送通知 — 激活沉睡的用戶,推送即時消息、公告通知,激發更新等。如web資訊客戶端、web即時通信工具、h5遊戲等運營產品。
  • 離線緩存 — 可編程攔截代理請求和返回,緩存文件,緩存的文件能夠被網頁進程取到(包括網絡離線狀態),將H5應用中不變化的資源或者不多變化的資源長久的存儲在用戶端,提高加載速度、下降流量消耗、下降服務器壓力
  • 事件同步 — 確保web端產生的任務即便在用戶關閉了web頁面也能夠順利完成。如web郵件客戶端、web即時通信工具等。
  • 定時同步 — 週期性的觸發Service Worker腳本中的定時同步事件,可藉助它提早刷新緩存內容。如web資訊客戶端

Service Worker特性

  • Service Worker 是一種可編程網絡代理,讓您可以控制頁面所發送網絡請求的處理方式
  • 它是一種 JavaScript Worker,沒法直接訪問 DOM
  • 必須在 HTTPS 環境下才能工做
  • Service Worker 普遍地利用了 promise,異步實現
  • Service Worker 在不用時會被停止,並在下次有須要時重啓, Service Worker線程中不能保存須要持久化的信息

瀏覽器支持狀況

看上圖能夠看出來,目前瀏覽器的支持狀況仍是很可觀的,基本上市場佔比很大的瀏覽器目前都是支持的,可喜可賀。

查看當前頁面是否有Service Worker

咱們打開瀏覽器的控制檯,查看Service Workers ,會給咱們展現出全部已經支持Service Workers 的網站

Service Worker 生命週期

主要包含六種狀態 解析成功(parsed),正在安裝(installing),安裝成功(installed),正在激活(activating),激活成功(activated),廢棄(redundant)。json

  • 解析成功(Parsed):首次註冊 Service Worker 時,瀏覽器解決腳本並得到入口點,若是解析成功,就能夠訪問到 Service Worker 註冊對象(registration object)
  • 正在安裝(Installing):解析完成後,瀏覽器會試着安裝,進入下一狀態,「installing」,在 installing 狀態中,Service Worker 腳本中的 install 事件被執行
  • 安裝成功/等待中(Installed/Waiting):若是安裝成功,Service Worker * 進入安裝成功(installed)(也稱爲等待中[waiting])狀態
  • 正在激活(Activating):處於 waiting 狀態的 Service Worker,在如下之一的狀況下,會被觸發activating 狀態:
    • 當前已無激活狀態的 workerService
    • Worker 腳本中的 self.skipWaiting() 方法被調用
    • 用戶已關閉 Service Worker 做用域下的全部頁面,從而釋放了此前處於激活態的 worker
    • 超出指定時間,從而釋放此前處於激活態的 worker
  • 激活成功(Activated):就能夠應對事件性事件 —— fetch 和 message
  • 廢棄(Redundant):
    • installing 事件失敗
    • activating 事件失敗
    • 新的 Service Worker 替換其成爲激活態 worker 咱們看一下在代碼中是如何體現的
// main.js
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js')
            .then(function (registration) {

                // 註冊成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
                if (registration.installing) {
                    // Service Worker is Installing
                    console.log('Service Worker is Installing')
                } else if(registration.waiting) {
                    // 這是更新新版本或自動更新緩存的絕佳時機
                    /*
                    * 當前沒有激活的 worker
                    * 若是在 Service Worker 的腳本中 self.skipWaiting() 被調用
                    * 若是用戶訪問其餘頁面並釋放了以前激活的 worker
                    * 在一個特定的時間過去後,以前一個激活的 worker 被釋放
                    */
                    console.log('Service Worker is Waiting')
                } else if(registration.active) {
                    // 激活成功, Service Worker 是一個能夠徹底控制網頁的激活 worker
                    console.log('Service Worker is active')
                }
            })
            .catch(function (err) {

                // 註冊失敗:(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}

window.onload = function() {
    document.body.append('PWA!')
}
複製代碼

Service Worker 事件

  • Install:Service Worker 註冊並安裝完成後,對站點離線訪問最關鍵的資源 URL 列表,它們一般也是關鍵請求鏈包含的文件,並將其緩存進 caches 中
  • Activate:安裝完成並激活後,一個管理老舊緩存的好地方, 在通常 PWA 中,咱們能夠結合版本號和緩存名,及時刪除過時緩存
  • Fetch:頁面受控後全部請求會被 Service Worker 「劫持」,根據資源類型動態返回緩存數據或請求新數據

在代碼中的具體實現segmentfault

// sw.js
/*
   sw.js 控制着頁面資源和請求的緩存
*/

// 監聽 service worker 的 install 事件,
// 對站點離線訪問最關鍵的資源 URL 列表,它們一般也是關鍵請求鏈包含的文件,並將其緩存進 caches 中
self.addEventListener('install', function (e) {
    // 若是監聽到了 service worker 已經安裝成功的話,就會調用 event.waitUntil 回調函數
    e.waitUntil(
        caches.open('v1').then(cache => {
            // 經過 cache 緩存對象的 addAll 方法添加 precache 緩存
            return cache.addAll([
                '/main.js',
                '/index.html',
                '/'
            ]);
        }).then(function() {
          // 跳過waiting,直接進入active
          console.log('Skip waiting!')
          return self.skipWaiting()
        })
    );
});

/*
    事件回調是一個管理老舊緩存的好地方
    在通常 PWA 中,咱們能夠結合版本號和緩存名,及時刪除過時緩存
*/
self.addEventListener('activate', function(e) {
  const cacheStorageKey = 'v1'
  e.waitUntil(
    Promise.all(
      caches.keys().then(cacheNames => {
        return cacheNames.map(name => {
          if (name !== cacheStorageKey) {
            return caches.delete(name)
          }
        })
      })
    ).then(() => {
      console.log('Clients claims.')
      // 經過clients.claim方法,更新客戶端上的server worker
      return self.clients.claim()
    })
  )
})

/*
對不一樣資源類型應用不一樣的請求/緩存策略
看看這些請求所請求的文件在咱們的緩存裏有沒有,有的話就直接從緩存裏拿,不用下載了。
這也是PWA最重要的功能之一
*/
self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request)
        .then(function (response) {
            // 檢測是否已經緩存過
            if (response) {
                return response;
            }

            var fetchRequest = event.request.clone();

            return fetch(fetchRequest).then(
                function (response) {
                    // 檢測請求是否有效
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }

                    var responseToCache = response.clone();

                    caches.open('v1')
                        .then(function (cache) {
                            cache.put(event.request, responseToCache);
                        });

                    return response;
                }
            );
        })
    );
});

// 推送消息
self.addEventListener('message', function(event) {
  // Do stuff with postMessages received from document
    console.log("SW Received Message: " + event.data);
});

複製代碼

參考文檔

相關文章
相關標籤/搜索