使用 service-worker 加強緩存及改進部署

緩存策略

workbox 是 google 出的關於 service worker 生成預緩存列表,緩存策略,Background API 的一個庫,綜合了自家之前 sw-toolbox 以及 sw-precache 的功能。javascript

workbox 介紹了幾種緩存策略,workbox-strategieshtml

Service Worker Cookbook 也對這幾種緩存策略作了介紹,caching-strategies前端

可是關於這些策略的原理以及如何使用,強烈推薦谷歌開發者文檔中的 離線指南java

使用緩存,咱們都會關心瀏覽器會提供多達的存儲空間,如下代碼能夠查看你的應用已使用了多少存儲空間以及有多大的配額react

navigator.storage.estimate().then(info => console.log(info.quota, info.usage))

另外也能夠在 chrome 的 devtool 中進行查看,Application -> clear storage -> usagewebpack

Service Worker in React

若是項目採用 create-react-app 腳手架搭建,內置了 sw-precache-webpack-plugin 這個離線化插件,因而就對它作了一些適配。它是基於 google 的 sw-precache 的一個插件。git

若是大家項目沒有使用 create-react-app,建議使用 workbox 的 webpack Plugin,workbox 也是 google 新出的關於 service-worker 的工具。github

若是大家的靜態資源不在 CDN 上,Create React APP 已幫你寫好了 webpack 的配置。web

若是靜態資源在 CDN 上,就要略微折騰一番了。chrome

靜態資源在 CDN 上

/index.html/sw.js 須要在同域下,引用 /sw.js 時須要注意去掉 PUBLIC_PATH (webpackConfig.output.publicPath) 的前綴。

另外 sw-precache-webpack-plugin 生成 preCache 列表時,也會對 /index.html 添加上 PUBLIC_PATH 的前綴,須要替換掉,配置以下。其中 paths.appBuild 爲 webpackConfig.output.path

{
  ...config,
  mergeStaticsConfig: true,
  stripPrefixMulti: {
    [`${paths.appBuild}/index.html`]: '/index.html'
  }
}

如下是對於爲什麼如此操做的源碼分析

關於 stripPrefixMulti ,能夠查看 sw-precache 的文檔,sw-precache#stripprefixmulti-object。主要是處理 precache 文件的前綴的,如如下 源碼

// https://github.com/GoogleChromeLabs/sw-precache/blob/5.2.1/lib/sw-precache.js#L170
var relativeUrl = fileAndSizeAndHash.file
  .replace(
    new RegExp('^(' + Object.keys(params.stripPrefixMulti)
        .map(escapeRegExp).join('|') + ')'),
    function(match) {
      return params.stripPrefixMulti[match];
    })
  .replace(path.sep, '/');

能夠看出來它用來替換特定前綴。

sw-precache-webpack-plugin 中已經對它作了一些處理,查看 源碼

// https://github.com/goldhand/sw-precache-webpack-plugin/blob/v0.11.5/src/index.js#L119
if (outputPath) {
  // strip the webpack config's output.path (replace for windows users)
  stripPrefixMulti[`${outputPath}${path.sep}`.replace(/\\/g, '/')] = publicPath;
}

它把 precache 文件列表的前綴所有替換爲了 publicPath (即 webpackConfig.output.publicPath),可是 /index.html 不能在 cdn 的路徑上,因此須要特殊配置一下。

stripPrefixMulti: {
  [`${paths.appBuild}/index.html`]: '/index.html'
}

根據正則的短路原則,恰好能夠把 index.html 給替換回來。

'hello, world'.replace(/(hello, world)|(hello)/, 'shanyue')    // shanyue

動態緩存 API

對於靜態資源,採起了全部靜態資源添加hash,除部署後第一次外均不需再訪問服務器。

若是這裏採用 workbox 的術語的話,那麼靜態資源則是採用了 Cache-First 的策略,當緩存不可取時纔回退到網絡,而對於動態 API,則採用 Network-First 的策略,只有在離線狀態下才使用緩存。

固然,若是你只想使用 service worker 作緩存控制的話,API 緩存就能夠跳過了。

如下代碼是 sw-precache-webpack-plugin 的配置,動態緩存利用了 google 的 sw-toolbox 工具,它提供瞭如 workbox 同樣的緩存策略。

{
  runtimeCaching: [{
    urlPattern: /api/,
    handler: 'networkFirst'
  }]
}

緩存 GraphQL query

GraphQL 的 query 是使用 http 的 POST 請求進行發送的,而 service worker 不支持對 POST 請求進行緩存。

Replaying POST requests by w3c@ServiceWorker

其實一想很正常,POST 是非冪等的,連 http 也不對它進行緩存。

GraphQL 的 query 支持 GET 請求,修改成 GET 是可行的。另一種方案是使用 apollo-cache-persist 對訪問過的數據進行持久化。

部署

關於前端項目在生產環境中部署的問題是一個比較工程化的問題,關於具體實現方案簡單來講是以下兩點

  1. 先部署資源,每次部署對靜態資源添加 hash 到文件名中,帶有hash的資源添加超長時間緩存(Cache-Control: public, max-age=31536000),不帶hash的資源配置 Etag,並配置 Cache-Control: no-cache
  2. 再部署頁面,並配置 Cache-Control: no-cache

能夠參考如下兩篇文章

可是有了 Service Worker 以後有以下幾個好處

  1. 部署資源與部署頁面順序能夠不嚴格控制 (仍是嚴格控制比較好一些)
    假設先部署頁面,後部署資源,用戶進入了新頁面,可是資源沒有更新,這時候 Service Worker 會在 install 事件中,因爲沒法找到新的資源而致使 install 失敗,資源進行回退。
  2. 節省帶寬
    之前用戶每次訪問頁面,須要向服務器請求 /index.html 與一切不帶 hash 的資源,如今全部的資源都被 sw-toolbox 或者 workbox 加上了指紋,每次只須要請求 /sw.js。

注意要對 /sw.js 設置 Cache-Control: no-cache

對比 http 緩存

http 的緩存策略雖然是把靜態資源緩存在瀏覽器中,可是緩存行爲的控制倒是在服務端的 - 如 http response 中的 Cache-Control。而 service worker 對緩存資源的控制權徹底在瀏覽器手中,而且能夠經過編程精度控制靜態資源,動態請求的數據等。

可是這不表明 service worker 能夠徹底控制 http 進行緩存控制,由於 http 不只僅緩存在瀏覽器中,還有代理緩存中。

在 http 的 Cache-Control 中有兩個參數,private 和 public。private 表明不被 proxy 所緩存,區別詳細以下

Private vs Public in Cache-Control

相關文章
相關標籤/搜索