淺談PWA

binbinsilk發表於2018-11-16

1.PWA是什麼

PWA全稱Progressive Web App,即漸進式WEB應用。

一個 PWA 應用首先是一個網頁, 可以通過 Web 技術編寫出一個網頁應用. 隨後新增上 App Manifest 和 Service Worker 來實現 PWA 的安裝和離線等功能

  • 可以新增至主螢幕,點選主螢幕圖示可以實現啟動動畫以及隱藏位址列
  • 實現離線快取功能,即使使用者手機沒有網路,依然可以使用一些離線功能
  • 實現了訊息推送

上述這些特性將使得 Web 應用漸進式接近原生 App。

2.PWA特性

  • 漸進式:能確保每個使用者都能開啟網頁
  • 響應式:PC,手機,平板,不管哪種格式,網頁格式都能完美適配
  • 離線應用:支援使用者在沒網的條件下也能開啟網頁,這裡就需要 Service Worker 的幫助
  • APP 化:能夠像 APP 一樣和使用者進行互動
  • 常更新:一旦 Web 網頁有什麼改動,都能立即在使用者端體現出來
  • 安全:PWA基於HTTPS協議
  • 可搜尋:能夠被引擎搜尋到
  • 推送:做到在不開啟網頁的前提下,推送新的訊息
  • 可安裝:能夠將 Web 想 APP 一樣新增到桌面
  • 可跳轉:只要通過一個連線就可以跳轉到你的 Web 頁面
PWA不是某種技術的描述,而是幾種技術的合集,如圖:

淺談PWA

3.PWA怎樣實現

      上面所說PWA可實現Web App 新增至主屏、可實現離線快取,在斷網或弱網狀態下依然可以使用一些離線功能,不影響Web App體驗以及可實現使用者在不開啟瀏覽器情況下實現類似於原生App離線訊息推送功能。

      那麼對於這些實現,PWA依賴於什麼?

      主要依賴於manifest.json和service worker(在專案中可寫為一個名為SW.js的檔案並引入專案)

      manifest.json以一個json格式的檔案被引入專案,它主要用來實現PWA頁面的新增至主屏、定義App啟動時的URL(因為PWA App本質上還是一個Web)等。

4.Service Worker

      Service Worker是Chrome團隊提出的一個Web API,旨在給Web應用程式提供高階的可持續的後臺處理能力。相比於曾經的Web Worker這種脫離主執行緒之外的快取解決方法,Service Worker是持久的。因為web worker是臨時的,每次做的事情的結果還不能被持久存下來,如果下次有同樣的複雜操作,還得費時間的重新來一遍。

淺談PWA

       Service Workers 就像介於伺服器和網頁之間的攔截器,能夠攔截進出的HTTP 請求,從而完全控制你的網站。

最主要的特點

  • 在頁面中註冊並安裝成功後,執行於瀏覽器後臺,不受頁面重新整理的影響,可以監聽和截攔作用域範圍內所有頁面的 HTTP 請求。
  • 網站必須使用 HTTPS。除了使用本地開發環境除錯時(如域名使用 localhost)
  • 執行於瀏覽器後臺,可以控制開啟的作用域範圍下所有的頁面請求
  • 單獨的作用域範圍,單獨的執行環境和執行執行緒
  • 不能操作頁面 DOM。但可以通過事件機制來處理
  • 事件驅動型服務執行緒
Service Worker最新瀏覽器支援性(詳情請見caniuse.com/#feat=servi…)

淺談PWA

4.1 Service Worker的使用
  • 由於 Service Worker 要求 HTTPS 的環境,我們通常可以藉助於 github page 進行學習除錯。當然一般瀏覽器允許除錯 Service Worker 的時候 host 為 localhost 或者 127.0.0.1 也是 ok 的。

  • Service Worker 的快取機制是依賴 Cache API 實現的

  • 依賴 HTML5 fetch API

  • 依賴 Promise 實現

4.1.1 註冊

     要安裝 Service Worker, 我們需要通過在 js 主執行緒(常規的頁面裡的 js )註冊 Service Worker 來啟動安裝,這個過程將會通知瀏覽器我們的 Service Worker 執行緒的 javaScript 檔案在什麼地方。

淺談PWA淺談PWA     

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js', {scope: '/'})
            .then(function (registration) {

                // 註冊成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(function (err) {

                // 註冊失敗:(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}複製程式碼

  • 這段程式碼首先是要判斷 Service Worker API 的可用情況,支援的話我們們才繼續談實現,否則免談了。

  • 如果支援的話,在頁面 onload 的時候註冊位於 /sw.js 的 Service Worker。

  • 每次頁面載入成功後,就會呼叫 register() 方法,瀏覽器將會判斷 Service Worker 執行緒是否已註冊並做出相應的處理。

  • register 方法的 scope 引數是可選的,用於指定你想讓 Service Worker 控制的內容的子目錄。本 demo 中服務工作執行緒檔案位於根網域, 這意味著服務工作執行緒的作用域將是整個來源。

    關於 register 方法的 scope 引數,需要說明一下:Service Worker 執行緒將接收 scope 指定網域目錄上所有事項的 fetch 事件,如果我們的 Service Worker 的 javaScript 檔案在 /a/b/sw.js, 不傳 scope 值的情況下, scope 的值就是 /a/b

    scope 的值的意義在於,如果 scope 的值為 /a/b, 那麼 Service Worker 執行緒只能捕獲到 path 為 /a/b 開頭的( /a/b/page1, /a/b/page2,...)頁面的 fetch 事件。通過 scope 的意義我們也能看出 Service Worker 不是服務單個頁面的,所以在 Service Worker 的 js 邏輯中全域性變數需要慎用。

  • then() 函式鏈式呼叫我們的 promise,當 promise resolve 的時候,裡面的程式碼就會執行。

  • 最後面我們鏈了一個 catch() 函式,當 promise rejected 才會執行。

       程式碼執行完成之後,我們這就註冊了一個 Service Worker,它工作在 worker context,所以沒有訪問 DOM 的許可權。在正常的頁面之外執行 Service Worker 的程式碼來控制它們的載入。

4.1.2 安裝

      在你的 Service Worker 註冊成功之後呢,我們的瀏覽器中已經有了一個屬於你自己 web App 的 worker context 啦, 在此時,瀏覽器就會馬不停蹄的嘗試為你的站點裡面的頁面安裝並啟用它,並且在這裡可以把靜態資源的快取給辦了。

      install 事件我們會繫結在 Service Worker 檔案中,在 Service Worker 安裝成功後,install 事件被觸發。

      install 事件一般是被用來填充你的瀏覽器的離線快取能力。為了達成這個目的,我們使用了 Service Worker 新的標誌性的儲存 cache API — 一個 Service Worker 上的全域性物件,它使我們可以儲存網路響應發來的資源,並且根據它們的請求來生成key。這個 API 和瀏覽器的標準的快取工作原理很相似,但是是隻對應你的站點的域的。它會一直持久存在,直到你告訴它不再儲存,你擁有全部的控制權。

      localStorage 的用法和 Service Worker cache 的用法很相似,但是由於localStorage 是同步的用法,所以不允許在 Service Worker 中使用。 IndexedDB 也可以在 Service Worker 內做資料儲存。

// 監聽 service worker 的 install 事件
this.addEventListener('install', function (event) {
    // 如果監聽到了 service worker 已經安裝成功的話,就會呼叫 event.waitUntil 回撥函式
    event.waitUntil(
        // 安裝成功後操作 CacheStorage 快取,使用之前需要先通過 caches.open() 開啟對應快取空間。
        caches.open('my-test-cache-v1').then(function (cache) {
            // 通過 cache 快取物件的 addAll 方法新增 precache 快取
            return cache.addAll([
                '/',
                '/index.html',
                '/main.css',
                '/main.js',
                '/image.jpg'
            ]);
        })
    );
});複製程式碼

  • 這裡我們 新增了一個 install 事件監聽器,接著在事件上接了一個 ExtendableEvent.waitUntil()方法——這會確保 Service Worker 不會在 waitUntil() 裡面的程式碼執行完畢之前安裝完成。

  • waitUntil() 內,我們使用了 caches.open() 方法來建立了一個叫做 v1 的新的快取,將會是我們的站點資源快取的第一個版本。它返回了一個建立快取的 promise,當它 resolved 的時候,我們接著會呼叫在建立的快取例項(Cache API)上的一個方法 addAll(),這個方法的引數是一個由一組相對於 origin 的 URL 組成的陣列,這些 URL 就是你想快取的資源的列表。

  • 如果 promise 被 rejected,安裝就會失敗,這個 worker 不會做任何事情。這也是可以的,因為你可以修復你的程式碼,在下次註冊發生的時候,又可以進行嘗試。

  • 當安裝成功完成之後,Service Worker 就會啟用。在第一次你的 Service Worker 註冊/啟用時,這並不會有什麼不同。但是當 Service Worker 更新的時候 ,就不太一樣了。

4.1.3 自定義請求響應

       走到這一步,其實現在你已經可以將你的站點資源快取了,你需要告訴 Service Worker 讓它用這些快取內容來做點什麼。有了 fetch 事件,這是很容易做到的。

      每次任何被 Service Worker 控制的資源被請求到時,都會觸發 fetch 事件,這些資源包括了指定的 scope 內的 html 文件,和這些 html 文件內引用的其他任何資源(比如 index.html 發起了一個跨域的請求來嵌入一個圖片,這個也會通過 Service Worker),這下 Service Worker 代理伺服器的形象開始慢慢露出來了,而這個代理伺服器的鉤子就是憑藉 scope 和 fetch 事件兩大利器就能把站點的請求管理的井井有條。

      你可以給 Service Worker 新增一個 fetch 的事件監聽器,接著呼叫 event 上的 respondWith() 方法來劫持我們的 HTTP 響應,然後你可以用自己的魔法來更新他們。

this.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (response) {
            // 來來來,代理可以搞一些代理的事情

            // 如果 Service Worker 有自己的返回,就直接返回,減少一次 http 請求
            if (response) {
                return response;
            }

            // 如果 service worker 沒有返回,那就得直接請求真實遠端服務
            var request = event.request.clone(); // 把原始請求拷過來
            return fetch(request).then(function (httpRes) {

                // http請求的返回已被抓到,可以處置了。

                // 請求失敗了,直接返回失敗的結果就好了。。
                if (!httpRes || httpRes.status !== 200) {
                    return httpRes;
                }

                // 請求成功的話,將請求快取起來。
                var responseClone = httpRes.clone();
                caches.open('my-test-cache-v1').then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                return httpRes;
            });
        })
    );
});複製程式碼

       我們可以在 install 的時候進行靜態資源快取,也可以通過 fetch 事件處理回撥來代理頁面請求從而實現動態資源快取。

兩種方式可以比較一下:

  • on install 的優點是第二次訪問即可離線,缺點是需要將需要快取的 URL 在編譯時插入到指令碼中,增加程式碼量和降低可維護性;

  • on fetch 的優點是無需更改編譯過程,也不會產生額外的流量,缺點是需要多一次訪問才能離線可用。

        除了靜態的頁面和檔案之外,如果對 Ajax 資料加以適當的快取可以實現真正的離線可用, 要達到這一步可能需要對既有的 Web App 進行一些重構以分離資料和模板。

4.1.4 Service Worker版本更新

    /sw.js 控制著頁面資源和請求的快取,那麼如果快取策略需要更新呢?也就是如果 /sw.js 有更新怎麼辦?/sw.js 自身該如何更新?

      如果 /sw.js 內容有更新,當訪問網站頁面時瀏覽器獲取了新的檔案,逐位元組比對 /sw.js 檔案發現不同時它會認為有更新啟動 更新演算法,於是會安裝新的檔案並觸發 install 事件。但是此時已經處於啟用狀態的舊的 Service Worker 還在執行,新的 Service Worker 完成安裝後會進入 waiting 狀態。直到所有已開啟的頁面都關閉,舊的 Service Worker 自動停止,新的 Service Worker 才會在接下來重新開啟的頁面裡生效。

4.1.5 自動更新所有頁面

      如果希望在有了新版本時,所有的頁面都得到及時自動更新怎麼辦呢?可以在 install 事件中執行 self.skipWaiting() 方法跳過 waiting 狀態,然後會直接進入 activate 階段。接著在 activate 事件發生時,通過執行 self.clients.claim() 方法,更新所有客戶端上的 Service Worker。

// 安裝階段跳過等待,直接進入 active
self.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([

            // 更新客戶端
            self.clients.claim(),

            // 清理舊版本
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (cacheName !== 'my-test-cache-v1') {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});複製程式碼

      另外要注意一點,/sw.js 檔案可能會因為瀏覽器快取問題,當檔案有了變化時,瀏覽器裡還是舊的檔案。這會導致更新得不到響應。如遇到該問題,可嘗試這麼做:在 Web Server 上新增對該檔案的過濾規則,不快取或設定較短的有效期。

4.1.6 手動更新Service Worker

在頁面中,可手動藉助 Registration.update() 更新。

var version = '1.0.1';

navigator.serviceWorker.register('/sw.js').then(function (reg) {
    if (localStorage.getItem('sw_version') !== version) {
        reg.update().then(function () {
            localStorage.setItem('sw_version', version)
        });
    }
});複製程式碼

      Service Worker 的特殊之處除了由瀏覽器觸發更新之外,還應用了特殊的快取策略: 如果該檔案已 24 小時沒有更新,當 Update 觸發時會強制更新。這意味著最壞情況下 Service Worker 會每天更新一次。

4.2 Service Worker生命週期

淺談PWA

當使用者首次導航至 URL 時,伺服器會返回響應的網頁。

  • 第1步:當你呼叫 register() 函式時, Service Worker 開始下載。
  • 第2步:在註冊過程中,瀏覽器會下載、解析並執行 Service Worker ()。如果在此步驟中出現任何錯誤,register() 返回的 promise 都會執行 reject 操作,並且 Service Worker 會被廢棄。
  • 第3步:一旦 Service Worker 成功執行了,install 事件就會啟用
  • 第4步:安裝完成,Service Worker 便會啟用,並控制在其範圍內的一切。如果生命週期中的所有事件都成功了,Service Worker 便已準備就緒,隨時可以使用了!

5.PWA實現舉例

5.1manifest.json實現新增至主螢幕

index.html

<head>
  <title>Minimal PWA</title>
  <meta name="viewport" content="width=device-width, user-scalable=no" />
  <link rel="manifest" href="manifest.json" />
  <link rel="stylesheet" type="text/css" href="main.css">
  <link rel="icon" href="/e.png" type="image/png" />
</head>複製程式碼

manifest.json

{
  "name": "Minimal PWA", // 必填 顯示的外掛名稱
  "short_name": "PWA Demo", // 可選  在APP launcher和新的tab頁顯示,如果沒有設定,則使用name
  "description": "The app that helps you understand PWA", //用於描述應用
  "display": "standalone", // 定義開發人員對Web應用程式的首選顯示模式。standalone模式會有單獨的
  "start_url": "/", // 應用啟動時的url
  "theme_color": "#313131", // 桌面圖示的背景色
  "background_color": "#313131", // 為web應用程式預定義的背景顏色。在啟動web應用程式和載入應用程式的內容之間建立了一個平滑的過渡。
  "icons": [ // 桌面圖示,是一個陣列
    {
    "src": "icon/lowres.webp",
    "sizes": "48x48",  // 以空格分隔的圖片尺寸
    "type": "image/webp"  // 幫助userAgent快速排除不支援的型別
  },
  {
    "src": "icon/lowres",
    "sizes": "48x48"
  },
  {
    "src": "icon/hd_hi.ico",
    "sizes": "72x72 96x96 128x128 256x256"
  },
  {
    "src": "icon/hd_hi.svg",
    "sizes": "72x72"
  }
  ]
}複製程式碼

5.2 Service Worker實現離線快取

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello Caching World!</title>
  </head>
  <body>
    <!-- Image -->
    <img src="/images/hello.png" />                 
    <!-- JavaScript -->
    <script async src="/js/script.js"></script>     
    <script>
      // 註冊 service worker
      if ('serviceWorker' in navigator) {           
        navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
          // 註冊成功
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(function (err) {                   
          // 註冊失敗 :(
          console.log('ServiceWorker registration failed: ', err);
        });
      }
    </script>
  </body>
</html>複製程式碼

注:Service Worker 的註冊路徑決定了其 scope 預設作用頁面的範圍。
如果 service-worker.js 是在 /sw/ 頁面路徑下,這使得該 Service Worker 預設只會收到 頁面/sw/ 路徑下的 fetch 事件。
如果存放在網站的根路徑下,則將會收到該網站的所有 fetch 事件。
如果希望改變它的作用域,可在第二個引數設定 scope 範圍。示例中將其改為了根目錄,即對整個站點生效。

service-worker.js

var cacheName = 'helloWorld';     // 快取的名稱  
// install 事件,它發生在瀏覽器安裝並註冊 Service Worker 時        
self.addEventListener('install', event => { 
/* event.waitUtil 用於在安裝成功之前執行一些預裝邏輯
 但是建議只做一些輕量級和非常重要資源的快取,減少安裝失敗的概率
 安裝成功後 ServiceWorker 狀態會從 installing 變為 installed */
  event.waitUntil(
    caches.open(cacheName)                  
    .then(cache => cache.addAll([    // 如果所有的檔案都成功快取了,便會安裝完成。如果任何檔案下載失敗了,那麼安裝過程也會隨之失敗。        
      '/js/script.js',
      '/images/hello.png'
    ]))
  );
});
  
/**
為 fetch 事件新增一個事件監聽器。接下來,使用 caches.match() 函式來檢查傳入的請求 URL 是否匹配當前快取中存在的任何內容。如果存在的話,返回快取的資源。
如果資源並不存在於快取當中,通過網路來獲取資源,並將獲取到的資源新增到快取中。
*/
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request)                  
    .then(function (response) {
      if (response) {                            
        return response;                         
      }
      var requestToCache = event.request.clone();  //          
      return fetch(requestToCache).then(                   
        function (response) {
          if (!response || response.status !== 200) {      
            return response;
          }
          var responseToCache = response.clone();          
          caches.open(cacheName)                           
            .then(function (cache) {
              cache.put(requestToCache, responseToCache);  
            });
          return response;             
    })
  );
});複製程式碼

注:為什麼用request.clone()和response.clone()
需要這麼做是因為request和response是一個流,它只能消耗一次。因為我們已經通過快取消耗了一次,然後發起 HTTP 請求還要再消耗一次,所以我們需要在此時克隆請求

5.3 Service Worker實現訊息推送

淺談PWA

  • 步驟一、提示使用者並獲得他們的訂閱詳細資訊
  • 步驟二、將這些詳細資訊儲存在伺服器上
  • 步驟三、在需要時傳送任何訊息

     不同瀏覽器需要用不同的推送訊息伺服器。以 Chrome 上使用 Google Cloud Messaging<GCM> 作為推送服務為例,第一步是註冊 applicationServerKey(通過 GCM 註冊獲取),並在頁面上進行訂閱或發起訂閱。每一個會話會有一個獨立的端點(endpoint),訂閱物件的屬性(PushSubscription.endpoint) 即為端點值。將端點傳送給伺服器後,伺服器用這一值來傳送訊息給會話的啟用的 Service Worker (通過 GCM 與瀏覽器客戶端溝通)。

步驟一+步驟二 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Progressive Times</title>
    <link rel="manifest" href="/manifest.json">                                      
  </head>
  <body>
    <script>
      var endpoint;
      var key;
      var authSecret;
      var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
      // 方法很複雜,但是可以不用具體看,只是用來轉化vapidPublicKey用
      function urlBase64ToUint8Array(base64String) {                                  
        const padding = '='.repeat((4 - base64String.length % 4) % 4);
        const base64 = (base64String + padding)
          .replace(/\-/g, '+')
          .replace(/_/g, '/');
        const rawData = window.atob(base64);
        const outputArray = new Uint8Array(rawData.length);
        for (let i = 0; i < rawData.length; ++i) {
          outputArray[i] = rawData.charCodeAt(i);
        }
        return outputArray;
      }
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('sw.js').then(function (registration) {
          return registration.pushManager.getSubscription()                            
            .then(function (subscription) {
              if (subscription) {                                                      
                return;
              }
              return registration.pushManager.subscribe({                              
                  userVisibleOnly: true,
                  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
                })
                .then(function (subscription) {
                  var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
                  key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
                  var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
                  authSecret = rawAuthSecret ?
                    btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
                  endpoint = subscription.endpoint;
                  return fetch('./register', {                                         
                    method: 'post',
                    headers: new Headers({
                      'content-type': 'application/json'
                    }),
                    body: JSON.stringify({
                      endpoint: subscription.endpoint,
                      key: key,
                      authSecret: authSecret,
                    }),
                  });
                });
            });
        }).catch(function (err) {
          // 註冊失敗 :(
          console.log('ServiceWorker registration failed: ', err);
        });
      }
    </script>
  </body>
</html>複製程式碼

步驟三 伺服器傳送訊息給service worker

app.js

const webpush = require('web-push');                 
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
webpush.setVapidDetails(                             
  'mailto:contact@deanhume.com',
  'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
  'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
);
app.post('/register', function (req, res) {           
  var endpoint = req.body.endpoint;
  saveRegistrationDetails(endpoint, key, authSecret); 
  const pushSubscription = {                          
    endpoint: req.body.endpoint,
    keys: {
      auth: req.body.authSecret,
      p256dh: req.body.key
    }
  };
  var body = 'Thank you for registering';
  var iconUrl = 'https://example.com/images/homescreen.png';
  // 傳送 Web 推送訊息
  webpush.sendNotification(pushSubscription,          
      JSON.stringify({
        msg: body,
        url: 'http://localhost:3111/',
        icon: iconUrl
      }))
    .then(result => res.sendStatus(201))
    .catch(err => {
      console.log(err);
    });
});
app.listen(3111, function () {
  console.log('Web push app listening on port 3111!')
});複製程式碼

service worker監聽push事件,將通知詳情推送給使用者

service-worker.js

self.addEventListener('push', function (event) {
 // 檢查服務端是否發來了任何有效載荷資料
  var payload = event.data ? JSON.parse(event.data.text()) : 'no payload';
  var title = 'Progressive Times';
  event.waitUntil(
    // 使用提供的資訊來顯示 Web 推送通知
    self.registration.showNotification(title, {                           
      body: payload.msg,
      url: payload.url,
      icon: payload.icon
    })
  );
});複製程式碼

6.總結

PWA的優勢

  • 可以將app的快捷方式放置到桌面上,全屏執行,與原生app無異
  • 能夠在各種網路環境下使用,包括網路差和斷網條件下,不會顯示undefind
  • 推送訊息的能力
  • 其本質是一個網頁,沒有原生app的各種啟動條件,快速響應使用者指令

PWA存在的問題

  • 支援率不高:現在ios手機端不支援pwa,IE也暫時不支援
  • Chrome在中國桌面版佔有率還是不錯的,安卓移動端上的佔有率卻很低
  • 各大廠商還未明確支援pwa
  • 依賴的GCM服務在國內無法使用
  • 微信小程式的競爭

儘管有上述的一些缺點,PWA技術仍然有很多可以使用的點。

  • service worker技術實現離線快取,可以將一些不經常更改的靜態檔案放到快取中,提升使用者體驗。
  • service worker實現訊息推送,使用瀏覽器推送功能,吸引使用者
  • 漸進式開發,儘管一些瀏覽器暫時不支援,可以利用上述技術給使用支援瀏覽器的使用者帶來更好的體驗。

針對公司公測平臺專案啟發,個人探索思考對於PWA針對公司產品研發的可行性觀點如下:

      PWA鑑於尚未成熟,但最大優勢是提升Web App的體驗,個人在瀏覽完美校園公測平臺的測試專案中對PWA的實際工作應用有所思考——首先,對於個人近期參與過的《失物招領》輕應用來說:失物招領中資訊流列表內的各個“丟失”“撿到”的帖子性質其實是一個長期存在的資訊帖,因為有的失去物品或撿到物品可能找回難度很大,其次,對於失去物和撿到物的資訊描述來說,每個帖子的內容相比於社交類資訊帖來說,資訊更改不會很頻繁,涉及的互動主要可能是頁面內的聯絡失主或撿到者功能,所以對於這種資訊流更新不會太頻繁但又適合非聯網或弱網情況下隨時隨地能檢視帖子資訊與釋出者聯絡(尤其是通過獲取聯絡電話、QQ號碼或微訊號這種直接聯絡方式)的情況下,個人觀點適合採用PWA技術,或者PWA與上上週志兵所介紹的Index DB相結合的方式來對這種應用進行改造,可以對後端伺服器等成本有效減輕。其次,看到公測平臺內有《課程表》應用和《校歷》應用,同樣可以採用PWA進行改造或開發,因為這兩種應用的資訊更新頻率不會太快,如課程表和校歷可能每學期會學校更新一次,且有的更新幅度不會太大,更新點不會太多,適合Service Worker進行快取處理(此處不代表Service Worker就不能處理相對大一點的快取)。

對於PWA的使用需要根據專案性質進行評估,希望有朝一日可以對一些內部專案實現PWA化,從前端角度減輕專案整體中的部分或整體成本,個人認為,前端開發人員的核心能力和競爭力非幾個框架或工具的掌握,而是使用正確的技術手段減少專案的相關成本。

但個人能力尚淺薄,希望繼續努力,先以掌握並精通相關必要技術為前提。