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

寫於 2017.08.15html

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

因爲PWA相關的文章太多,因此本文再也不對「什麼是PWA」,「PWA的生命週期」等基礎內容再次贅述。webpack

offline-plugin相關連接:git

1、自動生成service-worker.js

PWA的核心可謂是service-worker(之後簡稱SW),任何一個PWA都有且只有一個service-worker.js文件,用於爲SW添加資源列表,進行註冊、激活等生命週期操做。可是在webpack構建的項目中,生成一個service-worker.js可能會面臨兩個較大的問題:github

  • 一、webpack生成的資源多會生成一串hash,sw的資源列表裏面須要同步更新這些帶hash的資源;
  • 二、每次更新代碼,都須要經過更新sw文件版本號來通知客戶端對所緩存的資源進行更新。(其實只要這一次的sw代碼和上一次的sw代碼不同便可觸發更新,但使用明確的版本號會更加合適)。

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

相比與sw-precache-webpack-plugin,我的認爲offline-plugin具備以下優勢:chrome

  • 一、更多的可選配置項,知足更加細緻的配置要求;
  • 二、更爲詳細的文檔和例子;
  • 三、更新頻率相對更高,star數更多;
  • 四、自動處理生命週期,用戶無需糾結生命週期的坑;
  • *五、支持AppCache;
  • 六、自動生成manifest文件。
  • ...

2、基本使用

安裝

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

初始化

第一步,進入webpack.config:npm

// 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文件當中:json

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

ES6/Babel/TypeScript數組

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

通過上面的步驟,offline-plugin已經集成到項目之中,經過webpack構建便可。

3、配置

前面說過,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標準所廢棄,不建議使用。 可是因爲仍然有部分瀏覽器支持,因此插件默認提供這個功能。

4、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 當更新被接受時觸發。

5、降級方案

當某些時候咱們須要撤掉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('註銷失敗')
        })
      })
    }
  }
})
複製代碼

6、遇到的坑

在具體實踐中,遇到一個比較大的坑,就是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

7、添加到主屏

手機瀏覽器都提供了「添加到主屏」的功能,但普通的網站添加到主屏,僅僅是把網站的書籤放到桌面。若是要想把網站以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和其餘靜態資源一併打包到網站根目錄便可:

示例地址:

打開chrome開發者工具,進入到Application一列,選擇Manifest,就能夠看到效果了:

截止到目前(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打開

添加到主屏後打開

離線後從主屏打開

打開任務管理器

能夠看到,PWA不管從表現仍是功能,都像一個獨立的APP那樣存在。

8、尾聲

原來一直覺得蘋果對PWA支持很差,但經過此次實踐,能夠知道其實PWA也取得了極大的推動,開發者們能夠開心地搭建本身的PWA啦! 結論不能下太早。。。

相關文章
相關標籤/搜索