上篇文章整理了PWA离线应用的技术脉络和注意事项,本篇文章将记录使用Vue + Workbox + localforage 实现离线应用的实践过程。一、Vue工程改造Vue CLI 提供了一个官方的 ...
上篇文章整理了PWA离线应用的技术脉络和注意事项,本篇文章将记录使用Vue + Workbox + localforage 实现离线应用的实践过程。
Vue CLI 提供了一个官方的 PWA 插件,可以快速为 Vue 项目添加 PWA 支持。运行以下命令安装插件:
// 在vue工程目录下执行如下命令
vue add pwa
安装完成后,插件会自动生成以下文件:
src/registerServiceWorker.js:用于注册 Service Worker(已在入口文件引入)。
public/manifest.json:Web App Manifest 文件,PWA 的元数据(配置文件中使用)。
在项目根目录下修改 vue.config.js 文件,配置 PWA 插件的行为。以下是一个示例配置:
module.exports = {
...
pwa: {
name: '超体',
themeColor: '#FDEAEC',
msTileColor: '#000000',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
manifestPath: 'https://storage.360buyimg.com/xxx/manifest.json', // 由于部署的缘故,将上面的manifest.json文件路径替换为了绝对地址
// 配置 workbox 插件
workboxPluginMode: 'InjectManifest',
workboxOptions: {
importScripts: ['https://storage.googleapis.com/workbox-cdn/releases/5.1.4/workbox-sw.js'], // 引入workbox依赖文件
exclude: [/\.html$/], // 不缓存的文件,按需配置
swSrc: 'src/offline/service-worker.js', // 按实际路径引入
// ...其它 Workbox 选项...
}
}
}
编写 service-worker.js 文件
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js')
if (workbox) {
console.log(`Yay! Workbox is loaded`)
} else {
console.log(`Boo! Workbox didn't load`)
}
workbox.core.setCacheNameDetails({
prefix: 'jd-ct', // 缓存前缀
suffix: 'v0.0.1'
})
workbox.core.skipWaiting() // 强制等待中的 Service Worker 被激活
workbox.core.clientsClaim() // Service Worker 被激活后使其立即获得页面控制权
workbox.precaching.precacheAndRoute(self.__precacheManifest || []) // 设置预加载,构建后会生成预加载资源文件 precache-manifest.js
//html采用networkFirst策略,支持离线也能大体访问
workbox.routing.registerRoute(
/(.*\.html|.*pf.jd.com(\/?)|.*localhost(\/?)$)/,
new workbox.strategies.NetworkFirst({
cacheName: 'jd:ct:html',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 10,
})
]
})
);
// 缓存web的css资源
workbox.routing.registerRoute(
// Cache CSS files
/.*\.css/,
// 使用缓存,但尽快在后台更新
new workbox.strategies.StaleWhileRevalidate({
// 使用自定义缓存名称
cacheName: 'jd:ct:css',
})
)
// 缓存web的js资源
workbox.routing.registerRoute(
// 缓存JS文件
/(.*\.js|\/gljs)/,
// 使用缓存,但尽快在后台更新
new workbox.strategies.StaleWhileRevalidate({
// 使用自定义缓存名称
cacheName: 'jd:ct:js'
})
)
// 缓存web的json资源
workbox.routing.registerRoute(
// 缓存JS文件
/(.*\.json)/,
// 使用缓存,但尽快在后台更新
new workbox.strategies.StaleWhileRevalidate({
// 使用自定义缓存名称
cacheName: 'jd:ct:json'
})
)
// 缓存web的图片资源
workbox.routing.registerRoute(
function ({ url }) {
const reg = /\.(?:png|gif|jpg|jpeg|svg|webp|avif)$/
const isCache = reg.test(url.href)
return isCache
},
new workbox.strategies.CacheFirst({
cacheName: 'jd:ct:img',
plugins: [
new workbox.expiration.ExpirationPlugin({ // 此处的配置指在优化清除缓存资源
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 设置缓存有效期为30天
purgeOnQuotaError: true,
})
],
})
)
// ** 构建时自动生成,无需编写 **
// 该文件会记录构建文件和工程中的一些icon,资源内容需按需引用,毕竟预加载同样占用网络资源
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "f4421206102fa2971be968f4576b3753",
"url": "favicon.ico"
},
{
"revision": "d60ab2076b97d8a76e06",
"url": "js/chunk-vendors.f45b0b1a.js"
},
{
"revision": "d60ab2076b97d8a76e06",
"url": "js/chunk-vendors.f45b0b1a.js.map"
},
{
"revision": "148cadf1d0b64e5aa7d7",
"url": "js/index.7058d434.js"
},
{
"revision": "a26142391a19d57bd5745cf1cc159eca",
"url": "js/three-140.min.js"
},
{
"revision": "f147ae01feb6e9dbc161ae9e1befe720",
"url": "js/three.js"
}
]);
至此,我们的PWA应用已经改造完成了,构建后将会看到以下输出文件 index.html、precache-manifest.js、service-worker.js等
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png">
<link rel="manifest" href="https://storage.360buyimg.com/arvideo/chaoti/manifest.json">
<meta name="theme-color" content="#FDEAEC">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="xxx">
<link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png">
<link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#FDEAEC">
<meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileColor" content="#000000">
</head>
<body>
</body>
</html>
本地要访问不能使用file://方式,需要起个本地服务(推荐:http-server)
通过上面的改造实现了静态资源的离线访问,但部分场景可能依赖接口数据才能看到页面内容,针对此场景采用了indexdb的缓存方案,将部分接口数据存储到本地,提升应用的离线可用性。
import localforage from 'localforage'
localforage.config({
driver: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE],
name: 'jd-ct',
description: 'xxx',
})
export const atlasListStore = localforage.createInstance({
name: 'atlasList',
})
// 判断网络连接状态,按需调用本地数据or接口请求
export const getLocalForageData = (onlineCallback, offlineCallback) => {
if (navigator.onLine) {
onlineCallback && onlineCallback()
} else {
offlineCallback && offlineCallback()
}
}
// 业务代码示例
// 获取接口数据,并存储到本地
getAtlasList: function (isRefresh = false) {
getLocalForageData(() => {
// 获取远程数据
services.designTool
.GetDigitalPhotoList(this.searchParams)
.then((res) => {
if (res.code === 0) {
// 更新本地数据
(res.data.data || []).forEach((item) => {
atlasListStore.setItem(String(item.id), item)
})
} else {
return Promise.reject()
}
})
.catch(() => {
})
}, () => {
// 获取本地数据
const result = []
atlasListStore.iterate((value) => {})
.then(() => {})
.catch((err) => {
console.log('catch',err)
})
})
},
目前实用chrome对PWA应用调试也是非常方便的:
1)可以在 chrome://serviceworker-internals/ 页面中查看到全部PWA应用
2)也可以在控制台中查看
另外也可以查看indexdb缓存数据
1、项目迭代过程中需要维护server worker缓存版本,可通过suffix: 'v0.0.1'更新
2、虽然部分旧版浏览器不支持service worker,但在要求不是很高或者用户不是很广泛的场景下可使用
3、Service Worker 运行在独立线程中,调试相对复杂,workbox也有调试模式,缓存和命中会输出在控制台,可以减轻调试压力