藉助 workbox 將網站升級成 PWA

PWA(Progressive Web Apps)是谷歌近幾年一直在推動的 web 應用新模型。PWA 藉助 Service Worker 緩存網站的靜態資源,甚至是網絡請求,使網站在離線時也能訪問。而且咱們可以爲網站指定一個圖標添加在手機桌面,實現點擊桌面圖標便可訪問網站。css

Web App Manifest

Web App Manifest 是一個 JSON 文件,它用來定義網站添加到桌面的圖標以及從桌面圖標進入網站時的一系列行爲,如:啓動樣式,全屏主題等。html

先建立 manifest.jsonvue

{
  "name": "blog-pwa",
  "short_name": "blog-pwa",
  "icons": [
    {
      "src": "/img/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/img/icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#000000",
  "theme_color": "#4DBA87"
}

將文件引入:android

<link rel=manifest href=/manifest.json>

咱們能夠從開發者工具上看咱們的配置:webpack

圖片描述

icons 屬性定義了添加到桌面的圖標, display: standalone 表示咱們要從桌面全屏啓動,theme_color": "#4DBA87 是全屏啓動時手機頂部狀態欄的背景色,background_color": "#000000 是啓動頁的背景色,啓動頁目前不能定製,默認由 background_coloriconname 組合而成。git

Web App Manifest很簡單,只要照着文檔每一個屬性看一遍就行。github

Service Worker

Service Worker 是瀏覽器在後臺獨立於網頁運行的腳本。是它讓 PWA 擁有極快的訪問速度和離線運行能力。web

那它是如何作到的呢?咱們一步步來看。正則表達式

註冊 Service Worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/service-worker.js')
    .then(registration => {
      console.log(
        'ServiceWorker registration successful with scope: ',
        registration.scope
      )
    })
    .catch(err => {
      console.log('ServiceWorker registration failed: ', err)
    })
}

須要注意的是,Service Worker 腳本除了域名爲 localhost 時能運行在 http 協議下之外,只能運行 https 協議下。chrome

安裝

const CACHE_NAME = 'cache-v1'
const DATA_CACHE_NAME = 'data-cache-v1'

const PRE_CACHE = ['/index.html', '/css/app.css', '/js/app.js']

self.addEventListener('install', e => {
  console.log('[ServiceWorker] Install')
  e.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(PRE_CACHE)
    })
  )
})

在安裝的時候預緩存網站的靜態資源,任何資源路徑出錯都會形成 Service Worker 安裝失敗。

代理請求

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(response => {
      if (response) {
        return response
      }

      const fetchRequest = e.request.clone()

      return fetch(fetchRequest).then(response => {
        // Check if we received a valid response
        if (!response || response.status !== 200) {
          return response
        }

        const responseToCache = response.clone()

        caches.open(DATA_CACHE_NAME).then(cache => {
          cache.put(e.request, responseToCache)
        })

        return response
      })
    })
  )
})

安裝成功後,Service Worker 就能夠監聽網站的全部請求,匹配到緩存時直接返回,未匹配到時請求服務器,服務器成功返回時添加到緩存。

更新

如今網站的 Service Worker 已經能夠正常工做了,那如何更新它呢?

咱們只須要修改 Service Worker 文件就能夠更新它。當咱們每次訪問網站時都會去下載這個文件,當發現文件不一致時,就會安裝這個新 Service Worker ,安裝成功後,它將進入等待階段。當咱們關閉窗口從新導航到網站時(刷新網頁不行),新 Service Worker 將開始控制網站。舊 Service Worker 終止工做並觸發 activate 事件:

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keyList => {
      return Promise.all(
        keyList.map(key => {
          if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {
            console.log('[ServiceWorker] Removing old cache', key)
            return caches.delete(key)
          }
        })
      )
    })
  )
})

在其卸載時必定要刪除舊緩存,否則咱們的網站永遠沒法更新。

上面只簡單講了 Service Worker 如何工做。咱們會發現有不少問題須要咱們進一步解決:

  1. 預緩存的靜態資源修改後在下一次發版本時的文件名都不同,手動寫死過低效,最好每次都自動生成資源文件名。
  2. 緩存資源是以硬編碼字符串判斷是否有效,這樣每次發版本都須要手動修改,才能更新緩存。而且每次都是全量更新。可否以文件的粒度進行資源緩存呢?
  3. 請求代理沒有區分靜態資源和動態接口。已經緩存的動態接口也會一直返回緩存,沒法請求新數據。

上面只列出了三個明顯的問題,還有不少問題是沒有考慮到的。若是讓咱們本身來解決這些問題,不只是工做量很大,並且也很難寫出生產環境可用的 Service Worker

workbox

既然如此,咱們最好是站在巨人的肩膀上,這個巨人就是谷歌。workbox 是由谷歌瀏覽器團隊發佈,用來協助建立 PWA 應用的 JavaScript 庫。固然直接用 workbox 仍是太複雜了,谷歌還很貼心的發佈了一個 webpack 插件,可以自動生成 Service Worker 和 靜態資源列表 - workbox-webpack-plugin

只需簡單一步就能生成生產環境可用的 Service Worker

const { GenerateSW } = require('workbox-webpack-plugin')

new GenerateSW()

打包一下:

圖片描述

還能說什麼呢?谷歌大法好!固然這只是最簡單的可用版本,其實這裏有一個最嚴重的問題不知道有沒人發現,那就是 importScripts 引用的是谷歌域名下的 cdn ,這讓咱們牆內的網站怎麼用,因此咱們須要把這個問題解決並自定義一些配置加強 Service Worker 的能力:

new GenerateSW({
  importWorkboxFrom: 'local',
  skipWaiting: true,
  clientsClaim: true,
  runtimeCaching: [
    {
      // To match cross-origin requests, use a RegExp that matches
      // the start of the origin:
      urlPattern: new RegExp('^https://api'),
      handler: 'staleWhileRevalidate',
      options: {
        // Configure which responses are considered cacheable.
        cacheableResponse: {
          statuses: [200]
        }
      }
    },
    {
      urlPattern: new RegExp('^https://cdn'),
      // Apply a network-first strategy.
      handler: 'networkFirst',
      options: {
        // Fall back to the cache after 2 seconds.
        networkTimeoutSeconds: 2,
        cacheableResponse: {
          statuses: [200]
        }
      }
    }
  ]
})

首先 importWorkboxFrom 咱們指定從本地引入,這樣插件就會將 workbox 全部源文件下載到本地,牆內開發者的福音。上面提到過新 Service Worker 安裝成功後須要進入等待階段,skipWaiting: true 將使其跳過等待,安裝成功後當即接管網站,注意這個要和 clientsClaim 一塊兒設置爲 trueruntimeCaching 顧名思義是配置運行時如何緩存請求的,這裏只說一點,緩存跨域請求時 urlPattern 的值必須爲 ^ 開頭的正則表達式,其它的配置看文檔都能獲得詳細的介紹。

再打包一次:

圖片描述

如今咱們就能夠將打包好的代碼部署到網站上了,源碼在這,最後再上幾張圖:

圖片描述

動圖

參考

Web App Manifest

服務工做線程:簡介

服務工做線程生命週期

workbox-webpack-plugin

相關文章
相關標籤/搜索