使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

Jrain發表於2019-01-29

寫於 2017.08.15

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

談起PWA,許多人可能還只停留在“瞭解”的層面,比較少在實踐中真正地嘗試過,更多的僅僅是對著網上的教程和例子大概玩過。然而,網路上的例子多是簡單的demo,鮮有與真正的開發相結合,例如和webpack的工程化結合。這篇文章將會從一個webpack plugin出發,談一談如何使用這個名為offline-plugin的webpack外掛輕鬆實現PWA。

由於PWA相關的文章太多,所以本文不再對“什麼是PWA”,“PWA的生命週期”等基礎內容再次贅述。

offline-plugin相關連結:

一、自動生成service-worker.js

PWA的核心可謂是service-worker(以後簡稱SW),任何一個PWA都有且只有一個service-worker.js檔案,用於為SW新增資源列表,進行註冊、啟用等生命週期操作。但是在webpack構建的專案中,生成一個service-worker.js可能會面臨兩個較大的問題:

  • 1、webpack生成的資源多會生成一串hash,sw的資源列表裡面需要同步更新這些帶hash的資源;
  • 2、每次更新程式碼,都需要通過更新sw檔案版本號來通知客戶端對所快取的資源進行更新。(其實只要這一次的sw程式碼和上一次的sw程式碼不一樣即可觸發更新,但使用明確的版本號會更加合適)。

看到這你可能已經想到,萬能的webpack社群是否已經提供了相應的plugin來幫我們自動處理這些事情呢?答案是肯定的。除了官方推薦的sw-precache-webpack-plugin之外,還有我們今天的主角offline-plugin

相比與sw-precache-webpack-plugin,個人認為offline-plugin具有如下優點:

  • 1、更多的可選配置項,滿足更加細緻的配置要求;
  • 2、更為詳細的文件和例子;
  • 3、更新頻率相對更高,star數更多;
  • 4、自動處理生命週期,使用者無需糾結生命週期的坑;
  • *5、支援AppCache;
  • 6、自動生成manifest檔案。
  • ...

二、基本使用

安裝

npm install offline-plugin [--save-dev]
複製程式碼

初始化

第一步,進入webpack.config:

// webpack.config.js example

var OfflinePlugin = require('offline-plugin');

module.exports = {
  // ...

  plugins: [
    // ... other plugins
    // it's always better if OfflinePlugin is the last plugin added
    new OfflinePlugin()
  ]
  // ...
}
複製程式碼

第二步,把runtime新增到你的入口js檔案當中:

require('offline-plugin/runtime').install();
複製程式碼

ES6/Babel/TypeScript

import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install();
複製程式碼

經過上面的步驟,offline-plugin已經整合到專案之中,通過webpack構建即可。

三、配置

前面說過,offline-plugin支援細緻的配置,以滿足不同的需求。下面將介紹幾個比較常用的配置項,方便大家進一步使用。

  • Caches: 'all' | Object

    告訴外掛應該快取什麼東西,並以何種方式進行快取 all: 意味著所有webpack構建出來的資源,以及在externals選項中的資源都會被快取。 Object: 包含三個陣列或正則的配置物件(main, additional, optional),它們都是可選的,且預設為空。 預設:all

  • externals: Array 允許開發者指定一些外部資源(比如CDN引用,或者不是通過webpack生成的資源)。配合Cachesadditional項,能夠實現快取外部資源的功能。

    預設:null 舉例:['fonts/roboto.woff']

  • ServiceWorker: Object | null | false 該物件包含多個配置項,這裡僅列舉最常用的。

    events:布林值。允許runtime接受來自sw的訊息,預設值為false。 navigateFallbackURL:當一個URL請求從快取或網路都無法被獲取時,將會重定向到該選項所指向的URL。

  • AppCache: Object | null | false

    offline-plugin預設支援AppCache,但是AppCache草案已經被web標準所廢棄,不建議使用。 但是由於仍然有部分瀏覽器支援,所以外掛預設提供這個功能。

四、runtime

上一節介紹了offline-plugin在webpack當中的配置,這一節將介紹runtime的一些用法。 若要使offline-plugin生效,使用者必須在入口js檔案中通過runtime進行初始化操作:

// 通過AMD方式
require('offline-plugin/runtime').install();

// 或者通過ES6/Babel/TypeScript方式

import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install();
複製程式碼

OfflinePluginRuntime物件提供了下列三個方法:

  • install(options: Object) 開啟ServiceWorker/AppCache的安裝流程。這個方法是安全的,並且必須在頁面初始化的時候就被呼叫。另外請勿把它放在任何的條件語句之內。(這句話不全對,在後面的降級方案裡面會詳細介紹)

  • applyUpdate() 接受當前所安裝的sw的更新資訊。

  • update() 檢查新版本的ServiceWorker/AppCache的更新資訊。


runtime.install()方法接受一個配置物件引數,用於處理sw各個生命週期裡面的事件:

  • onInstalled 當ServiceWorker/AppCache被install時執行,可用於展示“APP已經支援離線訪問”。

  • onUpdating AppCache不支援該方法 當更新資訊被獲取且瀏覽器正在進行資源更新時觸發。在這個時刻,一些資源正在被下載。

  • onUpdateReadyonUpdating事件完成時觸發。這時,所有資源都已經下載完畢。 通過呼叫runtime.applyUpdate()方法來觸發更新。

  • onUpdateFailedonUpdating事件因為某些原因失敗時觸發。 這時沒有任何資源被下載,同時所有的資源更新程式都應該被取消或跳過。

  • onUpdated 當更新被接受時觸發。

五、降級方案

當某些時候我們需要撤掉sw進行降級的時候,我們需要主動登出sw。然而offline-plugin預設沒有提供登出sw的unregister()方法,所以我們需要自己實現。

其實要主動登出sw非常簡單,我們可以直接呼叫ServiceWorkerContainer.getRegistrations()方法來拿到registration例項,然後呼叫registration.unregister()方法即可,具體程式碼如下:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistration().then((registration) => {
    registration && registration.unregister().then((boolean) => {
      boolean ? alert('登出成功') : alert('登出失敗')
    });
  })
}
複製程式碼

在呼叫該方法後,sw已經被登出,重新整理一下頁面就能看到資源是重新從網路獲取的了。

在真實的生產環境中,我們可以通過呼叫介面,來決定是否使用降級方案:

fetch(URL).then((switch) => {
  if (switch) {
    OfflinePluginRuntime.install()
  } else {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.getRegistration().then((registration) => {
        registration && registration.unregister().then((boolean) => {
          boolean ? alert('登出成功') : alert('登出失敗')
        })
      })
    }
  }
})
複製程式碼

六、遇到的坑

在具體實踐中,遇到一個比較大的坑,就是sw.js檔案的更新。

在service worker的設計中,瀏覽器每一次載入站點的URL,都會重新請求一遍sw.js。若發現這一次的sw.js內容和上一次的不一樣,就會判定為資源更新,重新觸發sw的生命週期。然而,sw.js也是一個普通的js資原始檔,會預設使用伺服器設定的expired時間,也就是它的max-age。在理解了service worker的設計後,我們不難發現,sw.jsmax-age應該儘可能短,以便瀏覽器能夠及時更新資源列表。

這也是我在研究階段直接使用http-server時所發現的問題。後來在官方的例子中,我發現npm script裡面是這麼寫的:

"start": "http-server ./dist -p 7474 -c no-cache"
複製程式碼

直接指定了所有資源都不使用快取,這一點值得我們注意。

另外,webpack-dev-server裡無法正常使用offline-plugin,因為它需要具體的檔案去生成sw.js,但是通過webpack-dev-server構建的專案,其檔案是存放在記憶體中的,所以無法和offline-plugin正常搭配使用。建議僅在生產模式內使用offline-plugin

七、新增到主屏

手機瀏覽器都提供了“新增到主屏”的功能,但普通的網站新增到主屏,僅僅是把網站的書籤放到桌面。如果要想把網站以PWA的形式新增到主屏,我們需要一個manifest.json檔案

{
  "name": "offline-plugin",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "theme_color": "#181743",
  "background_color": "#181743",
  "start_url": "/",
  "display": "standalone"
}
複製程式碼

然後,把這個manifest.json和其他靜態資源一併打包到網站根目錄即可:

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

示例地址:

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

開啟chrome開發者工具,進入到Application一列,選擇Manifest,就可以看到效果了:

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

截止到目前(2017年8月15日),我所使用的iOS10.3.2版本的iPhone7手機,已經支援PWA了,效果如下: 經過查閱大量的資料,到目前為止,iOS並不支援PWA,但是可以通過在html裡面新增幾個標籤,實現web頁面和原生APP相似的體驗效果:

應用圖示:
<link rel="apple-touch-icon" href=“/custom_icon.png">

啟動畫面:
<link rel="apple-touch-startup-image" href="/launch.png">

應用名稱:
<meta name="apple-mobile-web-app-title" content="AppTitle">

全屏效果:
<meta name="apple-mobile-web-app-capable" content="yes">

設定狀態列顏色:
<meta name="apple-mobile-web-app-status-bar-style" content="black">
複製程式碼

使用safari開啟

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

新增到主屏後開啟

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

離線後從主屏開啟

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

開啟工作管理員

使用 offline-plugin 搭配 webpack 輕鬆實現 PWA

可以看到,PWA無論從表現還是功能,都像一個獨立的APP那樣存在。

八、尾聲

原來一直以為蘋果對PWA支援不好,但通過這次實踐,可以知道其實PWA也取得了極大的推進,開發者們可以開心地搭建自己的PWA啦! 結論不能下太早。。。

相關文章