淺談PWA

1.PWA是什麼

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

一個 PWA 應用首先是一個網頁, 能夠經過 Web 技術編寫出一個網頁應用. 隨後添加上 App Manifest 和 Service Worker 來實現 PWA 的安裝和離線等功能php

  • 能夠添加至主屏幕,點擊主屏幕圖標能夠實現啓動動畫以及隱藏地址欄
  • 實現離線緩存功能,即便用戶手機沒有網絡,依然可使用一些離線功能
  • 實現了消息推送

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

2.PWA特性

  • 漸進式:能確保每一個用戶都能打開網頁
  • 響應式:PC,手機,平板,無論哪一種格式,網頁格式都能完美適配
  • 離線應用:支持用戶在沒網的條件下也能打開網頁,這裏就須要 Service Worker 的幫助
  • APP 化:可以像 APP 同樣和用戶進行交互
  • 常更新:一旦 Web 網頁有什麼改動,都能當即在用戶端體現出來
  • 安全:PWA基於HTTPS協議
  • 可搜索:可以被引擎搜索到
  • 推送:作到在不打開網頁的前提下,推送新的消息
  • 可安裝:可以將 Web 想 APP 同樣添加到桌面
  • 可跳轉:只要經過一個鏈接就能夠跳轉到你的 Web 頁面
PWA不是某種技術的描述,而是幾種技術的合集,如圖:

3.PWA怎樣實現

      上面所說PWA可實現Web App 添加至主屏、可實現離線緩存,在斷網或弱網狀態下依然可使用一些離線功能,不影響Web App體驗以及可實現用戶在不打開瀏覽器狀況下實現相似於原生App離線消息推送功能。html

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

      主要依賴於manifest.json和service worker(在項目中可寫爲一個名爲SW.js的文件並引入項目)java

      manifest.json以一個json格式的文件被引入項目,它主要用來實現PWA頁面的添加至主屏、定義App啓動時的URL(由於PWA App本質上仍是一個Web)等。ios

4.Service Worker

      Service Worker是Chrome團隊提出的一個Web API,旨在給Web應用程序提供高級的可持續的後臺處理能力。相比於曾經的Web Worker這種脫離主線程以外的緩存解決方法,Service Worker是持久的。由於web worker是臨時的,每次作的事情的結果還不能被持久存下來,若是下次有一樣的複雜操做,還得費時間的從新來一遍。git


       Service Workers 就像介於服務器和網頁之間的攔截器,可以攔截進出的HTTP 請求,從而徹底控制你的網站。
github

最主要的特色web

  • 在頁面中註冊並安裝成功後,運行於瀏覽器後臺,不受頁面刷新的影響,能夠監聽和截攔做用域範圍內全部頁面的 HTTP 請求。
  • 網站必須使用 HTTPS。除了使用本地開發環境調試時(如域名使用 localhost)
  • 運行於瀏覽器後臺,能夠控制打開的做用域範圍下全部的頁面請求
  • 單獨的做用域範圍,單獨的運行環境和執行線程
  • 不能操做頁面 DOM。但能夠經過事件機制來處理
  • 事件驅動型服務線程
Service Worker最新瀏覽器支持性(詳情請見 caniuse.com/#feat=servi…)


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 文件在什麼地方。

     

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生命週期


當用戶首次導航至 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實現消息推送


  • 步驟1、提示用戶並得到他們的訂閱詳細信息
  • 步驟2、將這些詳細信息保存在服務器上
  • 步驟3、在須要時發送任何消息

     不一樣瀏覽器須要用不一樣的推送消息服務器。以 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化,從前端角度減輕項目總體中的部分或總體成本,我的認爲,前端開發人員的核心能力和競爭力非幾個框架或工具的掌握,而是使用正確的技術手段減小項目的相關成本。

但我的能力尚淺薄,但願繼續努力,先以掌握並精通相關必要技術爲前提。

相關文章
相關標籤/搜索