- 背景介绍:
为了提高React应用的启动速度、离线访问能力, 做到页面能离线启动、service worker能在后台默默更新本地缓存的页面、数据的版本,并且做到监控版本更新能力的靠谱性。
终极方案:采用serviceWorker的成熟方案workBox通过Webpack的workBox官方插件workBox-webpack-plugin做到页面能离线启动、service worker能在后台默默更新本地缓存的页面、数据的版本, 通过Google Analytics做到监控版本更新能力的靠谱性(上报应用版本)。
简单来说就是: workBox-webpack-plugin 和 Google Analytics 的故事。
进入正题之前, 先来一些开胃菜:
目录
workBox-webpack-plugin
- 安装
- 配置接入 (重点踩坑)
- 解析配置
React如何接入Google Analytics, 做到监控离线的应用版本
- 封装GA组件
- 配置GA, 上报数据及版本
- 后台查看上报的数据 (重点采坑)
甜品一: workBox-webpack-plugin
官方文档: developers.google.com/web/tools/w…
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
-
}
-
复制代码
-
if ('serviceWorker' in navigator) {
-
window.addEventListener('load', () => {
-
// 敲黑板, 这里的/workBox/workBoxServiceWorker.js需要根据实际情况变化, 因为我项目没部署到根域名, 所以加了workBox的路径名...
-
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
- cdn: 引入google的官方cdn, 后果是国内用户打开网站, 一脸懵逼的被墙 (所以肯定不能用这个默认值!!!)
- local: workBox人性化的在本地写出了workBox的代码, 然后和项目代码一起上传部署就ok, 但每个项目都要这样, 就很麻烦。
- disabled: 傲娇的不从谷歌引入, 也不导出的本地。但如果你不配置:
importScripts
的引入地址, 那将一脸懵逼。
所以我最终的方案:
-
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浏览器
- 谷歌, 火狐正常, 能更新最新版本缓存以及更新页面 (当发现代码有变化的时候)
- QQ浏览器 || UC浏览器: 这两个傻蛋, 把最主要的
workBoxServiceWorke.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 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求。( - - 你敢确定不会变吗...)
甜品二: React如何接入Google Analytics, 做到监控离线的应用版本
React项目接入GA, 封装GA, 使用了react-ga
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修饰器即可~
后台查看上报的数据