前端應該瞭解的PWA

1、傳統web 應用

當前web應用在移動時代並無達到其在桌面設備上流行的程度,下面有張圖來對比與原生應用之間的差異。

究其緣由,無外乎下面不可避免的幾點:css

  • 移動設備網絡限制-不可忽略的加載時間
  • web應用依賴於瀏覽器做爲入口
  • 體驗與原生的差距

假如能解決以上的幾點,對web app 來講會有多大的提高能夠想象。html

2、PWA是什麼

PWA 全稱Progressive Web Apps(漸進式Web應用程序),旨在使用現有的web技術提供用戶更優的使用體驗。 基本要求java

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

PWA 自己強調漸進式,並不要求一次性達到安全、性能和體驗上的全部要求,開發者能夠經過 PWA Checklist 查看現有的特徵。git

除以上的基準要求外,還應該包括如下特性:github

  • 漸進式 - 適用於全部瀏覽器,由於它是以漸進式加強做爲宗旨開發的
  • 鏈接無關性 - 可以藉助 Service Worker 在離線或者網絡較差的狀況下正常訪問
  • 相似應用 - 因爲是在 App Shell 模型基礎上開發,由於應具備 Native App 的交互和導航,給用戶 Native App 的體驗
  • 持續更新 - 始終是最新的,無版本和更新問題
  • 安全 - 經過 HTTPS 協議提供服務,防止窺探和確保內容不被篡改
  • 可索引 - 應用清單文件和 Service Worker 可讓搜索引擎索引到,從而將其識別爲『應用』
  • 粘性 - 經過推送離線通知等,可讓用戶迴流
  • 可安裝 - 用戶能夠添加經常使用的 webapp 到桌面,免去去應用商店下載的麻煩
  • 可連接 - 經過連接便可分享內容,無需下載安裝

看起來有點眼花繚亂,這又是一個新的飛起的輪子嗎?這裏重申一下,PWA背後不是一種新的技術,而是集合當前多種web技術的一種集合。分別利用各自的功能來完成漸進式的總體需求。下面就沿着前面提出的問題分別瞭解一下相關技術web

3、技術組成

由如下幾種技術構成:chrome

  • App Manifest
  • Service Worker
  • Notifications API
  • Push API

其中Service Worker是PWA技術的關鍵,它們可讓app知足上面的三基準。其餘技術則是錦上添花,讓app更加的強大。json

3.1 service worker背景

離線緩存背景

針對網頁的體驗,從前到後都作了不少努力,極力去下降響應時間,這裏就不表述多樣的技術手段。 另外一個方向的就是緩存,減小與服務器非必要的交互,不過對於離線的狀況下瀏覽器緩存就無力了, 這樣離線緩存的需求就出現了。數組

離線緩存的歷程

web應用在離線緩存發展的過程當中也不是一簇而就的,經歷了逐漸完善的過程。
初期的解決方案是AppCache 然而,事實證實這是一個失敗的嘗試,缺陷太多,已經被廢棄了。具體能夠查看Application Cache is a douchebag 可是方向仍是正確的,那就繼續孜孜不倦的探索。promise

workers

持久化先放一邊,來談談另外一個問題 基於瀏覽器中的 javaScript 單線程的現實逐漸不能知足現代web需求的現狀,例如耗時的計算,用戶的交互顯然會受影響。 爲了將這些耗時操做從主線程中解放出來,早期W3C新增了一個Web Worker 的 API,能夠脫離主線程單獨執行,而且能夠與主線程交互。 不過Web Worker是臨時性的依賴於建立頁面 ,不能知足咱們持久化的需求。 衝着這個目標,下面就比較容易解決了,搞個能持久存在的就好了。 在Web Worker的基礎上,W3C新增了service worker來知足咱們持久化的需求。 其生命週期與頁面無關,關聯頁面未關閉時,它也能夠退出,沒有關聯頁面時,它也能夠啓動 功能

Service Worker雖然知足了離線緩存來,其功能可不只僅侷限於此。 能夠提供

  • 豐富的離線體驗,
  • 週期的後臺同步,
  • 消息推送通知,
  • 攔截和處理網絡請求,
  • 管理資源緩存 這些正好也是PWA的目的,因此說Service Worker是PWA的關鍵技術。

前提條件

Service Worker 出於安全性和其實現原理,在使用的時候有必定的前提條件。

  • 因爲 Service Worker 要求 HTTPS 的環境
    固然通常瀏覽器容許調試 Service Worker 的時候 host 爲 localhost 或者 127.0.0.1
  • Service Worker 的緩存機制是依賴 Cache API (略過)
  • 依賴 HTML5 fetch API(略過)
  • 依賴 Promise 實現
    由上可知,不是全部的瀏覽器都支持的,支持狀況大概以下:
iOS 內的全部的瀏覽器都基於 safari,因此iOS要在11.3以上 IE是放棄支持了,不過Edge好歹支持了。

3.2 Cache

Cache是Service Worker衍生出來的API,配合Service Worker實現對資源請求的緩存。 不過cache並不直接緩存字符串,而是直接緩存資源請求(css、js、html等)。
cache也是key-value形式,通常來講key就是request,value就是response

  • caches.open(cacheName) 打開一個cache
  • caches是global對象,返回一個帶有cache返回值的Promise
  • cache.keys() 遍歷cache中全部鍵,獲得value的集合
  • cache.match(Request|url) 在cache中匹配傳入的request,返回Promise;
  • cache.matchAll只有第一個參數與match不一樣,須要一個request的數組,固然返回的結果也是response的數組
  • cache.add(Request|url) 並非單純的add,由於傳入的是request或者url,在cache.add內部會自動去調用fetch取回request的請求結果,而後纔是把response存入cache;
  • cache.addAll相似,一般在sw install的時候用cache.addAll把全部須要緩存的文件都請求一遍
  • cache.put(Request, Response) 這個至關於cache.add的第二步,即fetch到response後存入cache
  • cache.delete(Request|url) 刪除緩存

3.3 註冊Service Worker

註冊即聲明sw文件的位置,顯然應該在主js中引入。大概以下:

//基於promise
function registerServiceWorker(){
    // 註冊service worker
    return navigator.serviceWorker.register('./sw1.js').then(registration => {
        console.log('註冊成功');
        // 返回
        return registration;
    })
    .catch(err => {
        console.error('註冊失敗', err);
    });
}
window.onload = function () {
    //是否支持
    if (!('serviceWorker' in navigator)) {
        return;
    }
    registerServiceWorker()
}
複製代碼

3.4 生命週期

Service worker 有一個獨立於web 頁面的生命週期。 若是在網站上安裝 serice worker ,你須要註冊,註冊後瀏覽器會在後檯安裝 service worker。而後進入下面的不一樣階段。 激活以後,service worker 將控制全部的頁面,歸入它的範圍,不過第一次在頁面註冊 service worker 時不會控制頁面,直到它再次加載。 service worker 生效以後,它會處於下面兩種狀態之一:

  • service worker 終止來節省內存,
  • 頁面發起網絡請求後,它將處理請求獲取和消息事件。

由上圖看知,分爲這麼幾個階段:

  • Installing
    發生在 Service Worker 註冊以後,表示開始安裝,觸發 install 事件回調指定一些靜態資源進行離線緩存
  • Installed Service Worker 已經完成了安裝,而且等待其餘的 Service Worker 線程被關閉。
  • Activating 在這個狀態下沒有被其餘的 Service Worker 控制的客戶端,容許當前的 worker 完成安裝
  • Activated
    在這個狀態會處理 activate 事件回調 (提供了更新緩存策略的機會)。並能夠處理功能性的事件 fetch (請求)、sync (後臺同步)、push (推送)
  • Redundant 被替換,即被銷燬

瞭解聲明週期實際上是爲了咱們在不一樣時間段去監聽事件來完成相應操做。對PWA來講主要兩個事件。

  • install 事件回調:

event.waitUntil():傳入一個 Promise 爲參數,等到該 Promise 爲 resolve 狀態爲止。 self.skipWaiting():self 是當前 context 的 global 變量,執行該方法表示強制當前處在 waiting 狀態的 Service Worker 進入 activate 狀態。

  • activate 回調:

event.waitUntil():傳入一個 Promise 爲參數,等到該 Promise 爲 resolve 狀態爲止。 self.clients.claim():在 activate 事件回調中執行該方法表示取得頁面的控制權, 這樣以後打開頁面都會使用版本更新的緩存。舊的 Service Worker 腳本再也不控制着頁面,以後會被中止。

const CURCACHE = 'CURCACHE_test_1'
const RUNTIME = 'runtime';
const CURCACHE_URLS = [
    './',
    '/asset/sw.jpg',
    'index.js'
]
self.addEventListener('install',e=>{
    e.waitUntil(
      //存儲緩存路徑對應的資源
        caches.open(CURCACHE).then(cache=>{
            cache.addAll(CURCACHE_URLS)
        }).then(
            self.skipWaiting()
        )
    )
})
 
 
   
  //代理請求,使用緩存,請求發送以前
  self.addEventListener('fetch', e => {
    e.respondWith(
      //緩存是否匹配 
      caches.match(e.request).then(function(response) {
        if (response != null) {
          //命中緩存返回緩存,結束請求
          return response
        }
        //未命中緩存,正常請求
        return fetch(e.request.url)
      })
    )
  });
複製代碼

更新service worker service worker 更新步驟以下:

  • 更新 service worker 的文件
    網頁打開時服務器會進行對比,保持最新
  • 新的 service worker 啓動install
  • 當前頁面生效的依然是老的service worker,新的 service worker 會進入 「waiting」 狀態。
  • 頁面關閉以後,老的 service worker 會被幹掉,新的 servicer worker 接管頁面
  • 新的 service worker 生效後會觸發 activate 事件。
const CURCACHE = 'precache_test_1'
//假設上個版本的key爲precache_test_2 反正不等於CURCACHE
self.addEventListener('activate', e => {
  e.waitUntil(
      //遍歷當前緩存keys
      caches.keys().then(cacheNames=>{
        return Promise.all(
          cacheNames.map(function(cacheName) {
            //是否等於當前key,保留本身
            if (cacheName !== CURCACHE) {
              return caches.delete(cacheName);
            }
          })
    )}).then(() => self.clients.claim())
 )
}) 
複製代碼

這樣一個簡單的service worker離線緩存完成了。控制檯能夠看到,來源是service worker

關閉網絡以後再次訪問,能夠一樣獲得上面的結果,而且sw.js請求未能拿到,可是不影響,舊的文件依然在,這裏證實了每次都回去對比sw文件以確保更新 到這裏,離線緩存就實現了。

4、添加到主屏幕

容許將站點添加至主屏幕,是 PWA 提供的一項重要功能。這樣就不用再依賴於瀏覽器做爲平臺,符合移動端的用戶習慣。

manifest.json

須要 manifest.json 文件去配置應用的圖標、名稱等基本信息以下:

{
    //被提示安裝應用時出現的文本
    "name": "PQJ-PWA",
    //添加至主屏幕後的文本
    "short_name":"PQJ",
    "description": "測試demo",
    //添加以後,啓動地址
    "start_url": "/index.html",
    //圖標信息
    "icons": {
      "128": "/asset/sw.jpg"
    },
    "developer": {
      "name": "pqj",
      "url": ""
    },
    "display": "standalone",
    "background_color": "#287fc5",
    "theme_color": "#fff",
    "permissions": {
        "desktop-notification": {
          "description": "Needed for creating system notifications."
        }
      }
}  
複製代碼

而後以以下方式在html中引入

<link rel="manifest" href="/mainfest.json" />
複製代碼

這樣完成以後,移動端安卓使用chrome(親測),首次訪問時會提示是否容許安裝到主屏幕,以應用icon的形式出現。 圖片和文字即由配置決定。

5、消息通知

消息通知也是使用service worker的通知功能進行的,容許服務器想用戶發生通知,而非用戶主動請求才去響應某些行爲。
正常的通知邏輯須要服務器來參與實現,此次展現只實現功能。

  • 首先申請通知權限
  • 註冊service worker
  • 處理邏輯,發送通知
function getPermission(){
    return new Promise((resolve, reject) => {
        //權限獲取
        const permissionPromise = Notification.requestPermission(result => {
            resolve(result);
        });
    }).then(result => {
            //判斷條件
            if (result === 'granted') {
                execute();
            }
            else {
                console.log('no permission');
            }
        });
} 
複製代碼

發送通知

function execute() {
    // 容許以後執行
    registerServiceWorker().then(registration => {
        // 通知
        registration.showNotification('Hello World!');
    });
}  
複製代碼

結束語

參考文檔

lavas.baidu.com/doc
developer.mozilla.org/zh-CN/Apps/…

至此,本文介紹就結束了,更多請參考實例雖然PWA目前來看,面對的限制還不少,可是也能夠看出web組織在更好的提高web應用方向上作的努力。正如一直提到的那句話,將來可期。 目前國內百度這方面作的比較成熟,新浪微博已經有了pwa 測試版。

相關文章
相關標籤/搜索