Hello PWA

作者:永無止晉

2016年 Google 在 I/O 大會上提出一個 Next Web Generation 的概念 —— PWA 橫空出世,2017年始開始流行,目前雖未完全成熟,但越來越火爆,可以預見未來會廣爲流行。你還在等什麼?

一、Hello PWA

PWA(Progressive Web App) [1], 漸進式 WEB 應用,是提升 Web App 的體驗的一種方法,給用戶原生應用的體驗。PWA 可以通過 Service Worker, Manifest 等新技術讓站點具備離線可用、添加到桌面、實時消息提醒等功能,從功能和體驗上無限接近原生 App。

1.1. 背景

自1990年萬維網之父-蒂莫西·約翰·“蒂姆”·伯納·李爵士,創建了 HTTP、HTML 和 WorldWideWeb (全世界第一個網頁瀏覽器)以來,Web 技術和影響力在以驚人的速度增長。HTML5,CSS3,Webpack,React,VUE,Babel,SPA 等技術的成熟與發展彷彿讓 Web 進入了百家爭鳴的春秋時期,Web應用能做的事情越來越多,大家對web的希望也越來越高。

但隨着移動時代的到來,web 應用因爲不能離線訪問,沒有快捷入口和頁面頻繁卡頓等開始失寵。除了原生應用因離線能力,瞬時加載和可靠性強等優點爆炸性崛起外,Hybrid ,React Native 等 APP 開發模式似乎也有點如日中天的“趕腳”。作爲一名 Web 前端開發工程師已經瑟瑟發抖,你呢? 莫慌,老大哥 Google 的工程師們早就“抖”完了,並在2015年提出2016年推出 PWA ,號稱 PWA 將成爲 Web 顛覆者的契機。

從上圖我們可以看出除了原生功能體驗、渲染性能,支持設備底層訪問,網絡要求等四個方面外, Web App 對比 Native、Hybrid、React Native,還是佔據一定優勢的。更讓人興奮的是 PWA 一定程度上解決了 Web App 的“劣勢”(圖中黃色背景部分),讓 Web APP 在保留原有優勢的基礎上漸進式接近原生 App。

1.2. 主要特點&優勢

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

以上列舉的是PWA的三個最主要的特點,要想了解所有特點,可到 PWA官網 [2] 查看。

1.3. 主要技術

PWA並不是描述一個技術,而是一個技術的合集,包含以下幾個主要技術:

1. Service Worker (詳見本文第二節);

2. App Manifest (詳見本文第三節);

3. Push API :允許 Web 應用擁有接收服務器並推送消息的能力( Web App 內部的消息推送)。目前已得到安卓和 PC 上新版本主流瀏覽器的支持,ios平臺不兼容;

4、 Notifications API :允許 Web 應用向用戶顯示系統通知。目前已兼容大部分 PC 主流瀏覽器,尚不兼容移動端瀏覽器。

5、 Background Sync :可延遲發送用戶行爲,直到用戶網絡連接穩定。目前幾乎不兼容移動端瀏覽器,PC 上 Firefox、 Chrome、 Safari、 Edge 等瀏覽器均已兼容。可解決兩個常見問題:
– 普通的頁面發起的請求會隨着瀏覽器進程的結束/或者 Tab 頁面的關閉而終止;
– 無網環境下,沒有一種機制能“維持”住該請求,以待有網情況下再進行請求。

二、Service Worker

2.1. 什麼是 Service Worker?

將你的網絡請求想象成飛機起飛,Service Worker 是路由請求的空中交通管制員。它可以通過網絡加載,或甚至通過緩存加載。

空中交通管制員可以延遲甚至改變飛機的降落的機場,Service Worker 的行爲方式也是如此,它可以重定向你的請求,甚至徹底停止。如上圖所示,Service Worker 在瀏覽器和後端服務之間起到了“管制員”的作用,它可以讓你全權控制網站發起的每一個請求,這爲許多不同的使用場景開闢了可能性,離線訪問只是其中一種。

2.2. 功能特性

關於 Service Worker 的功能特性,以下幾點看似無聊,其實很重要,不妨開發過程遇到問題再回頭看看。

  • 要求 HTTPS 環境,開發過程中,一般瀏覽器也允許 host 爲 localhost 或者 127.0.0.1;
  • 運行在它自己的全局腳本上下文中;
  • 不綁定到具體的網頁,無法修改網頁中的元素,因爲它無法訪問 DOM;
  • 一旦被 install,就永遠存在,除非被手動 unregister;
  • 異步實現,內部大都是通過 Promise 實現,依賴 HTML5 fetch API [3];
  • Service Worker 的緩存機制是依賴 Cache API [4] 實現的;

2.3. 生命週期

Service Worker 包含以下幾個生命週期:

  • 正在安裝(installing) :發生在 Service Worker 註冊之後,表示開始安裝,觸發 install 事件回調指定一些靜態資源進行離線緩存, install 事件回調中有兩個方法:
    • event.waitUntil() :傳入一個 Promise 爲參數,等到該 Promise 爲 resolve 狀態爲止。
    • self.skipWaiting() :執行該方法表示強制當前處在 waiting 狀態的 Service Worker 進入 activate 狀態。
  • 已安裝(installed) :安裝完成,等待其他的 Service Worker 線程被關閉。
  • 正在激活(activating) :處於 activating 狀態期間,Service Worker 腳本中的 activate 事件被執行。我們通常在 activate 事件中,清理 cache 中的文件。 activate 回調中有以下兩個方法:
    • event.waitUntil() :傳入一個 Promise 爲參數,等到該 Promise 爲 resolve 狀態爲止。
    • self.clients.claim() :在 activate 事件回調中執行該方法表示取得頁面的控制權, 這樣之後打開頁面都會使用版本更新的緩存。舊的 Service Worker 腳本不再控制着頁面,之後會被停止。
  • 已激活(activated) :在這個狀態會處理 activate 事件回調 (提供了更新緩存策略的機會)。並可以處理功能性的事件 fetch (請求)、 sync (後臺同步)、 push (推送)等。
  • 廢棄狀態(Redundant) :這個狀態表示一個 Service Worker 的生命週期結束。進入廢棄 ( redundant ) 狀態的原因可能爲這幾種:
    1. 安裝 ( install ) 失敗
    2. 激活 ( activating ) 失敗
    3. 新版本的 Service Worker 替換了它併成爲激活狀態

2.4. 主要事件

Service Worker 是基於事件的,安裝、激活、緩存、通信等操作都是需要在特定事件下操作,包含以下幾個主要事件。

2.5. 使用 Service Worker 緩存

上面一小節,我們瞭解了常用的幾個事件,本節讓我們一起利用這些事件緩存資源。(看代碼的時候注意註釋哦)

(1) 註冊

首先要註冊 Service Worker, 我們需要註冊 Service Worker 來啓動安裝。 sw.js 文件推薦在 HTML 當中引入:

if ('serviceWorker' in navigator) {
    //如果瀏覽器支持 Service Worker API,在頁面 onload 的時候註冊位於 /sw.js 的 Service Worker。
    //啓動一個線程很耗時,建議放到onload事件中 
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js', {scope: '/'})//scope 指定網域目錄上所有事項的 fetch 事件
            .then(function (registration) { // 註冊成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(function (err) {  // 註冊失敗
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}

(2) 通過 install 事件做靜態緩存

接着我們往 sw.js 文件中添加邏輯,我們先嚐試用 install 事件來做靜態緩存。

爲了幫助大家更好的理解下文的代碼,請先熟悉上文生命週期中的幾個方法和以下幾個 Service Worker 的全局變量:

var cacheName = 'helloPwa1'; //緩存的名稱                
self.addEventListener('install', event => { //安裝
  //確保 Service Worker 不會在 waitUntil() 裏代碼執行完畢之前安裝完成。
  event.waitUntil( 
    //用我們指定的緩存名稱來打開緩存     
    caches.open(cacheName)      
    //把 JavaScript 和 圖片文件添加到緩存中       
    .then(cache => cache.addAll([            
        './js/script.js',
        './images/hello.png',
        './images/logo.webp',
    ]))
  );
});
/*敲黑板*/
//如果任何文件下載失敗了,那麼安裝過程也會隨之失敗。如果文件列表很長會增加緩存失敗的機率導致 Servicer Worker 無法安裝。

(3) 通過 fetch 事件使用緩存和處理動態緩存

Service Worker 會攔截瀏覽器所有請求,並查詢當前 cache,如果存在 cache 則直接返回,若不存在,則通過 fetch 方法向服務端發起請求,並返回請求結果給瀏覽器。

//監聽fetch事件來攔截請求
self.addEventListener('fetch', function(event) { 
  event.respondWith(
    //當前請求是否匹配緩存中存在的任何內容
    caches.match(event.request) 
    .then(function(response) {
      if (response) {
        //如果匹配的話,就此返回緩存並不再繼續執行 
        return response;
      }
        //克隆了請求。請求是一個流,只能消耗一次。(很重要一步)
      var requestToCache = event.request.clone();
      //嘗試按預期一樣發起原始的 HTTP 請求
      return fetch(requestToCache).then( 
        function(response) {
          //如果由於任何原因請求失敗或者服務器響應了錯誤代碼,則立即返回錯誤信息
          if (!response || response.status !== 200) {
            return response;
          }
          //再一次克隆響應,因爲我們需要將其添加到緩存中,而且它還將用於最終返回響應
          var responseToCache = response.clone();
          //打開名稱爲 “helloWorld” 的緩存
          caches.open(cacheName) 
          .then(function(cache) {
            // 將響應添加到緩存中
            cache.put(requestToCache, responseToCache); 
          });
          return response;
        }
      );
    })
  );
});

(4) 通過 active 事件更新緩存

當我們將資源緩存後,除非註銷 sw.js 或手動清除緩存,否則新的靜態資源無法緩存。這個時候在我們可以在 activate 事件中檢查 cacheName 是否變化,如果變化則表示有了新的緩存資源,則將原有緩存刪除。所以在 sw.js 中加入以下代碼後,當需要更新緩存時,我們僅僅需要修改 cacheName 就可以了。

self.addEventListener('activate', function (e) {
    var cachePromise = caches.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            if (key !== cacheName) {
                return caches.delete(key);
            }
        }));
    })
    e.waitUntil(cachePromise);
    return self.clients.claim();
});

除了通過 active 事件更新緩存,我們還可以在註冊 Service Worker 的時候藉助 Registration.update() 更新。

var version = '1.0.1';//每次更新改這個版本號即可
navigator.serviceWorker.register('/sw.js').then(function (registration) {
    if (localStorage.getItem('sw_version') !== version) {
        registration.update().then(function () {
            localStorage.setItem('sw_version', version)
        });
    }
});

2.6. 兼容性

2.7. 小結

咳咳咳,關於 Service Worker,本文先聊這麼多,後續會有專文跟大家討論,請持續我們的JDC網站或全棧探索微信公衆號。

三、Manifest

3.1. Manifest是什麼?

manifest 的目的是將Web應用程序安裝到設備的主屏幕,爲用戶提供更快的訪問和更豐富的體驗。

3.2. 安裝 Web App 到主屏幕條件

  • 站點支持 HTTPS 訪問;
  • 站點部署 manifest.json ;
  • 站點註冊 Service Worker;

3.3. manifest.json

PWA 添加至桌面的功能實現依賴於 manifest.json 文件,一個基本的 manifest.json 文件應包含如下信息:

{
    "name": "莎士比亞", //web app 的名稱
    "short_name": "莎士比亞", //簡稱,沒有足夠空間的時候顯示
    "description": "人工智能撰稿與設計平臺", //簡介
    "icons": [{ //圖標
        "src": "./icon8.png",
        "sizes": "150x150",
        "type": "image/png"
    }, {
        "src": "./icon1.png",
        "sizes": "250x250",
        "type": "image/png"
    }],
    "background_color": "#fff", //背景顏色
    "theme_color": "#000", //主題色
    "start_url": "../index.html", //Web App 啓動時的html文件,地址路徑相對於mainfest.json文件
    "display": "standalone", //顯示類型: 包含 fullscreen,standalone,minimal-ui,browser
    "orientation": "portrait" //顯示方向,包含橫屏,豎屏,自適應等
}

3.4. 引用manifest.json

manifest.json 配置的 start_url 對應的 html 文件的 head 標籤中按如下方式引用即可:

<link rel="manifest" href="https://jdc.jd.com/archives/manifest.json">

3.5. 使用 manifest 實現 Web APP 的啓動頁

啓動頁示例

通過配置 manifest.json 的下列屬性,可以很容易的實現上圖的 Web App 啓動頁:

  1. 設置圖像和標題 :標題則直接取自 name。瀏覽器會從 icons 中選擇最接近 128dp 的圖片作爲啓動畫面圖像。
  2. 設置啓動背景顏色 :支持 #ffffff , #fff , white , rgb(255,255,255) 格式,其他不支持,如 rgba 。
  3. 設置啓動顯示類型 :僅當顯示類型 display 設置爲 standalone 或 fullscreen 時,PWA 啓動的時候纔會顯示啓動畫面。

3.6. 小結

看到這裏,你已經可以動手去把你的 Web APP 添加到桌面啦,由於兼容性問題,建議在 Android 或 PC 上使用 Chrome 瀏覽器體驗。(PC 上需要配置谷歌瀏覽器,打開“chrome://flags/”中允許Desktop PWAs即可)

寫在最後的話

本文旨在和大家一起學習 PWA 的入門知識,分別介紹了 PWA 的現狀,特點以及其核心技術 Service Worker 和 Manifest。後續還會有文章和大家一起深入學習 PWA。歡迎各位提出問題和建議,共同成長和進步。 讓熱愛 Web 的我們一起書寫 Web 的未來!

擴展閱讀

[1] https://developers.google.com/web/progressive-web-apps/

[2] https://developers.google.cn/web/progressive-web-apps/checklist

[3] https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API

[4] https://developer.mozilla.org/zh-CN/docs/Web/API/Cache

文章來源於 全棧探索 微信公衆號,掃描下面二維碼關注:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章