为了提升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修饰器便可~
添加方法: ( 自定义->自定义报告 )