PWA 在KLOOK的實踐經驗

本篇主要分享PWA在客路旅行實踐經驗:javascript

  • PWA簡介
  • 本地 https 調試
  • PWA 配置
  • 線上部署
  • PWA 更新
  • PWA 降級處理

主要技術棧:html

  • vue
  • PWA
  • webpack
  • nginx

本方案更適合已是SPA架構,想要升級PWA的項目,(service-worker.js後面簡稱sw)前端

PWA簡介

離線應用vue

  • 通常前端加載優化模板以及資源緩存在用戶就近的CDN(對比電商物流前置倉)
  • 引入PWA後包括模板的資源直接本地緩存(對比用戶家裏的售賣機)

能夠去這裏 outweb.io/ 感覺下PWA應用,會看到國內不少出名的站點也陸續升級PWA了java

PWA 調試

配置https本地環境利於pwa項目的調試, 本地證書生成能夠參考mkcert
nginx配置:node

listen       443 ssl;
        server_name  localhost2;
        ssl_certificate      /Users/liuze/localhost2.pem;
        ssl_certificate_key  /Users/liuze/localhost2-key.pem;
複製代碼

這邊是SPA項目,nginx主要作靜態資源輸出,更多關於服務端配置可參考vue 服務端配置webpack

location @ipad-index {
            add_header Cache-Control no-cache;
            expires 0;
            root /Users/liuze/workspace/klook-fe/klook-gds-hotel-ipad/dist/;
            try_files /index.html =404;
        }

        location /ipad {
            alias /Users/liuze/workspace/klook-fe/klook-gds-hotel-ipad/dist/;
            autoindex on;
            try_files $uri @ipad-index;
        }
        
         location /pwa-check { #檢測是否降級處理PWA
            proxy_pass http://127.0.0.1:3008;
            proxy_pass_request_headers on;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
複製代碼

(注意在 hosts 添加 127.0.0.1 localhost2 映射)nginx

PWA配置

配置主要基於 workbox-webpack-plugin 3.6.3/workbox-sw.js,選擇的緣由以下:git

  • 相對原生,極大簡單化了配置
  • 從2018年開始下載量一路飆升到如今,npm trends
  • 支持離線GA

主要配置基本以下,後面會分析到:github

workboxOptions: {
            importScripts:['./sw-prefile.js'],//插入生成的service-worker.js中
            runtimeCaching: [{
                urlPattern: /v1/,
                handler: "networkFirst",
                options: {
                    cacheName: "klook-gds-hotel-ipad-static",
                    expiration: {
                        maxEntries: 50,
                        purgeOnQuotaError: true,
                        maxAgeSeconds: 60 * 60 * 24,
                    },
                    cacheableResponse: {
                        statuses: [0, 200]
                    }
                }
            }],
            clientsClaim: true, //新的service-worker 將本身設置爲controller and triggers a "controllerchange" event
            offlineGoogleAnalytics: true,
            navigateFallback: '/ipad/index.html',
            directoryIndex: '/ipad/index.html'
        }
複製代碼

workbox-webpack-plugin主要提供兩種模式:

  1. GenerateSW 模式根據配置生成sw文件,適用場景:

    • sw只是涉及到簡單配置
    • 不涉及Web Push
  2. InjectManifest 模式經過既有sw文件再加工,適用場景;

    • 涉及Web Push
    • 更復雜的自定義配置

這裏使用的GenerateSW模式;

線上部署

線上部署與本地調試配置相似,除了根據部署項目靜態資源build目錄來調整nginx指向外,還須要進行證書替換

PWA更新

PWA控制頁面,更新不當很容易致使重大頁面錯誤;
這裏選擇用戶主動更新方式;

  1. 監聽sw的更新情況
  2. 若有更新就自動觸發sw時,同時發送自定義事件
  3. 自定義事件被觸發,顯示更新按鈕
  4. 用戶點擊更新按鈕觸發更新

實際中:

registerServiceWorker.js

//add interval check after registered
 registered(registration) {
      console.log('Service worker has been registered.')
      updateInterval = setInterval(() => {
        registration.update();//dynamically pull service-worker
        console.log('checking update!')
      }, 1000 * 10) // e.g 10s senconds checks
    }
    
//trigger custom event and export resgitration instance
    updated(registration) { //triggered whens service-worker.js changed
          console.log('New content is available; please refresh.')
          document.dispatchEvent(
            new CustomEvent('swUpdated', {
          detail: registration
        })
      );
    }
複製代碼

App.vue

//add custom event
    document.addEventListener(
      'swUpdated', this.showRefreshUI, { once: true }
    );
      
    //show refresh button
    showRefreshUI(e) {
      this.registration = e.detail;
      this.updateExists = true;
    },
    //click to refresh and post web worker message
    refreshApp() {
      this.updateExists = false;
      if (!this.registration || !this.registration.waiting) { return; }
      this.registration.waiting.postMessage({
        type: 'SKIP_WAITING'
      });
    },
複製代碼

這裏refreshApp主要是經過web worker進行message交互(解決了多tab同步更新問題)

有發送消息確定就有接收方,還記得以前有個配置項:

importScripts:['./sw-prefile.js']
複製代碼

sw-prefile.js

self.addEventListener('message', (event) => {
     if (event.data && event.data.type === 'SKIP_WAITING') {
        self.skipWaiting();
     }
 });
複製代碼

主要往sw中注入message事件監聽,使得接收消息時新的sw跳過waiting階段,再結合配置項

clientsClaim:true
複製代碼

完成了新舊sw替換。

到這裏彷佛是皆大歡喜
如今頁面已經由新的sw接管,不過前面的請求經過的舊的sw來,就形成一個頁面存在兩個版本的請求,因此還須要進一步處理

navigator.serviceWorker && navigator.serviceWorker.addEventListener( //triggered by registration.claim
      'controllerchange', () => {
        if (this.refreshing) return;
        this.refreshing = true;
        console.log('controllerchange triggered, -> auto refresh!!')
        window.location.reload();
      }
    );
複製代碼

監聽新的sw接管,而後主動觸發一次頁面的刷新,刷新後的就是徹底新的sw接管的頁面

PWA降級處理

如今主要經過定時發送信息處理降級:

  1. 定時check '/pwa-check'
  2. 根據返回斷定是否註銷PWA
  3. 註銷的同時清理定時check更新的任務

這裏經過node提供/pwa-check接口

小結:

Todos:

  • pwa轉換amp
  • 前端監控信息離線發送
  • 添加到桌面統計
  • ...

歡迎糾錯!

附錄

關於咱們
客路旅行正在開放前端,後端開發等崗位,這裏工做1075,不打卡

參考資料:

相關文章
相關標籤/搜索