爲了提升React應用的啓動速度、離線訪問能力, 作到頁面能離線啓動、service worker能在後臺默默更新本地緩存的頁面、數據的版本,而且作到監控版本更新能力的靠譜性。html
終極方案:採用serviceWorker的成熟方案workBox經過Webpack的workbox官方插件workbox-webpack-plugin作到頁面能離線啓動、service worker能在後臺默默更新本地緩存的頁面、數據的版本, 經過Google Analytics作到監控版本更新能力的靠譜性(上報應用版本)。react
簡單來講就是: workbox-webpack-plugin 和 Google Analytics 的故事。webpack
進入正題以前, 先來一些開胃菜:web
PWA(Progressive Web Apps)npm
serviceWorkerapi
serviceWorker僅支持本地(localhost/127.0.0.x)的http協議和帶有安全證書的https協議瀏覽器
Service Worker 是瀏覽器在後臺獨立於網頁運行的腳本。是它讓 PWA 擁有極快的訪問速度和離線運行能力。緩存
workBox安全
workbox-webpack-pluginmarkdown
官方文檔: developers.google.com/web/tools/w…
安裝: npm install workbox-webpack-plugin -D
配置: webpack插件中使用~
webpack配置中引入插件
const { GenerateSW } = require('workbox-webpack-plugin') exports.override = (webpackConfig, options) => { webpackConfig.plugins.push(new GenerateSW({ swDest: 'workboxServiceWorker.js', // 注意點1: 不寫這個名字, 插件默認會生成 service-worker.js 這個文件,而後不知道WHO又生成了一次service-worker.js這個文件(內容不是workbox預期), 因此webpack生成的workbox的腳本就這樣被替換了! 致使插件配置好的文件其實沒被寫出!!! // 當咱們每次訪問網站時都會去下載這個文件,當發現文件不一致時,就會安裝這個新 Service Worker ,安裝成功後,它將進入等待階段。 importWorkboxFrom: 'disabled', // 可填`cdn`,`local`,`disabled`, 區別下面整理 importScripts: 'https://fds.api.x.net/workbox-cdn/workbox-sw.js', // 我從本身的cdn引入了workbox,這樣就不用每一個項目都上傳 // 這三個都寫true skipWaiting: true, // 新 Service Worker 安裝成功後須要進入等待階段,skipWaiting: true 將使其跳過等待,安裝成功後當即接管網站。 clientsClaim: true, // 當即接管 offlineGoogleAnalytics: true, // 離線也記錄ga數據, 有網了再上報的意思。 cleanupOutdatedCaches: true, // 嘗試刪除老版本緩存 // 緩存規則, 具體下面記錄, 更詳細的請查閱文檔。 目前只緩存api runtimeCaching: [ { urlPattern: /^https:\/\/easy-mock\.com\//, handler: 'NetworkFirst', options: { cacheName: 'cached-api', networkTimeoutSeconds: 2, expiration: { maxEntries: 50, maxAgeSeconds: 1 * 24 * 60 * 60, // 1 day }, cacheableResponse: { statuses: [0, 200], }, }, }, ], })) return webpackConfig } 複製代碼
public
下的index.html模板裏的script標籤裏寫(別說我教大家的)if ('serviceWorker' in navigator) { window.addEventListener('load', () => { // 敲黑板, 這裏的/workbox/workboxServiceWorker.js須要根據實際狀況變化, 由於我項目沒部署到根域名, 因此加了workbox的路徑名... // 注意: 這裏有個坑 workboxServiceWorker 會被緩存, 解決方案在下面的坑點介紹 navigator.serviceWorker.register('/workbox/workboxServiceWorker.js').then(registration => { console.log('SW registered: ', registration) }).catch(registrationError => { console.log('SW registration failed: ', registrationError) }) }) } 複製代碼
swDest: 'workboxServiceWorker.js'
官方文檔中, 這個選項是可選填, 默認值爲: service-worker.js
。我遇到的問題是, 若是不寫這個從新寫出一個文件, 不知道是哪一個"B", 也寫出了一個叫service-worker.js
的文件, workBox的先寫出來了, 而後又被一個同名文件寫出覆蓋了! 而後你自認爲接入了workbox
, 實際上你不知道你接入的是啥。(有可能這個文件也是workbox寫出的,可是的確不是我想要的,雖然能實現緩存,但怎麼實現的,以及實現的徹底不是你想要的效果, 它徹底沒有引入workbox, 對, 故事就是這樣。)
importWorkboxFrom
和 importScripts
importWorkboxFrom能夠選填三個值: cdn
,local
,disabled
importScripts
的引入地址, 那將一臉懵逼。因此我最終的方案:
importWorkboxFrom: 'disabled', importScripts: 'https://fds.api.x.net/workbox-cdn/workbox-sw.js', // 把local模式導出的文件, 先部署獲取到cdn連接, 在寫死就ok 複製代碼
runtimeCaching
: 具體的運行時緩存策略經過這個選項配置, 具體的須要實戰或者根據本身的業務調整, 注意下面第四點, runtimeCaching中無需放置代碼頁面的緩存
緩存分爲precache
和 runningCache
, 打包以後的代碼, 會本身加入到precache中, 因此無需再運行時配置緩存資源, 好比:
具體預緩存的文件能夠看precache-manifest.xxxxxx.js
在文檔中搜索precache
, 有更多能夠配置的, 好比: include/exclude || chunks/excludeChunks
// 不必!!! runtimeCaching: [{ // cdn資源,這個本來想緩存的是代碼,實則已經被預緩存了 urlPattern: new RegExp('^https://cdn.net'), handler: 'staleWhileRevalidate', options: { cacheableResponse: { statuses: [200], }, }, },] 複製代碼
測試了PC端: 谷歌, 火狐, QQ瀏覽器, UC瀏覽器 || 移動端: QQ瀏覽器, miui瀏覽器
workboxServiceWorke.js
這個文件竟然自動硬盤緩存了!!! 致使讀取不到最新的代碼版本, 去獲取最新版的代碼!解決方案
方法一:
`/workbox/workboxServiceWorker.js?${Date.now()}` // 在workboxServiceWorker.js 後加上時間戳, 禁止被緩存!!! 複製代碼
方法二: FDS上配置workboxServiceWorker.js的響應頭, 禁止緩存
主要解析runtimeCaching中的緩存策略 (只在demo中測試, 沒接正式項目, 不知道有沒有更多的坑點)
Stale While Revalidate (主要)
這種策略的意思是當請求的路由有對應的 Cache 緩存結果就直接返回,在返回 Cache 緩存結果的同時會在後臺發起網絡請求拿到請求結果並更新 Cache 緩存,若是原本就沒有 Cache 緩存的話,直接就發起網絡請求並返回結果,這對用戶來講是一種很是安全的策略,能保證用戶最快速的拿到請求的結果,可是也有必定的缺點,就是仍是會有網絡請求佔用了用戶的網絡帶寬。
用來作CSS,JS,PNG等資源的策略, 以爲蠻好。
Network First (次主要)
這種策略就是當請求路由是被匹配的,就採用網絡優先的策略,也就是優先嚐試拿到網絡請求的返回結果,若是拿到網絡請求的結果,就將結果返回給客戶端而且寫入 Cache 緩存,若是網絡請求失敗,那就讀取Cache中的數據,這種策略通常適用於返回結果不太固定或對實時性有要求的請求,爲網絡請求失敗進行兜底。
用來作API接口的,也許就是這樣。
Cache First
這個策略的意思就是當匹配到請求以後直接從 Cache 緩存中取得結果,若是 Cache 緩存中沒有結果,那就會發起網絡請求,拿到網絡請求結果並將結果更新至 Cache 緩存,並將結果返回給客戶端。這種策略比較適合結果不怎麼變更且對實時性要求不高的請求。
Network Only
比較直接的策略,直接強制使用正常的網絡請求,並將結果返回給客戶端,這種策略比較適合對實時性要求很是高的請求。
Cache Only
這個策略也比較直接,直接使用 Cache 緩存的結果,並將結果返回給客戶端,這種策略比較適合一上線就不會變的靜態資源請求。( - - 你敢肯定不會變嗎...)
GA官方上報的字段及含義: developers.google.com/analytics/d…
import React from 'react' import ReactGA from 'react-ga' // eslint-disable-next-line export default function withTracker(WrappedComponent, option = {}) { const trackingId = 'UA-xxxxxxx-x' const trackPage = page => { ReactGA.initialize(trackingId, { gaOptions: { siteSpeedSampleRate: 100, // 上報網站速度的比例, 默認10%, 若是網站量比較大, 那就不用設置100%了。 }, }) const app = { appName: 'workBoxDemo', appVersion: '1.3', } // 上報版本 console.log(app) ReactGA.set(app) ReactGA.pageview(page) } // eslint-disable-next-line const HOC = class extends React.Component { componentDidMount() { const page = this.props.location.pathname trackPage(page) } componentWillReceiveProps(nextProps) { const currentPage = this.props.location.pathname const nextPage = nextProps.location.pathname if (currentPage !== nextPage) { trackPage(nextPage) } } render() { return <WrappedComponent {...this.props} /> } } return HOC } 複製代碼
而後在須要引入GA的頁面的react加上withTracker修飾器便可~
添加方法: ( 自定義->自定義報告 )