爲了提升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-plugin網絡
官方文檔: 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修飾器便可~
添加方法: ( 自定義->自定義報告 )