第一章中,我们加入Manifest ,让我们的网站生产图标放在手机桌面上,入口更加的明显了,和native差不多,大大的提升了用户体验,免去用户收藏网址等操作。
这章中,我们将加入PWA的核心:service-worker
相关代码:https://github.com/hwq888/yeye-PWA
下面简单的介绍一下,本章主要说怎么和VUE结合使用
service worker 是一个 WEB API,是 Web Workers 的一种实现,功能是可以拦截、处理请求,配合 CacheStorage 等 api,可以做到将资源存储到本地等,实现离线访问。
service workers 的生命周期有:
'use strict'
const version = 'xinwen_v1.4'
const offlineResources = [
'/',
'./static/images/default.png',
'./static/json/offline.json'
]
// 过滤需要加入缓存的api
const addApiCache = [
/https?:\/\/www.easy-mock.com\//
]
// 需要缓存的请求
const shouldAlwaysFetch = (request) => {
return addApiCache.some(regex => request.url.match(regex))
}
/**
* common function
*/
function cacheKey () {
return [version, ...arguments].join(':')
}
function log () {
console.log('SW:', ...arguments)
}
// 缓存 html 页面
function shouldFetchAndCache (request) {
return (/text\/html/i).test(request.headers.get('Accept'))
}
/**
* onClickNotify
*/
function onClickNotify (event) {
var action = event.action
console.log(`action tag: ${event.notification.tag}`, `action: ${action}`)
switch (action) {
case 'show-book':
console.log('show-book')
break
case 'contact-me':
console.log('contact-me')
break
default:
console.log(`未处理的action: ${event.action}`)
action = 'default'
break
}
event.notification.close()
event.waitUntil(
// event.waitUntil(async function() {
// 获取所有clients
self.clients.matchAll().then(function (clients) {
// debugger
if (!clients || clients.length === 0) {
return
}
clients.forEach(function (client) {
// 使用postMessage进行通信
client.postMessage(action)
})
})
)
}
/**
* Install 安装
*/
function onInstall (event) {
log('install event in progress.')
event.waitUntil(
caches.open(cacheKey('offline'))
.then(cache => cache.addAll(offlineResources))
.then(() => log('installation complete! version: ' + version))
.then(() => self.skipWaiting())
)
}
/**
* Fetch
*/
// 当网络离线或请求发生了错误,使用离线资源替代 request 请求
function offlineResponse (request) {
// debugger
log('(offline)', request.method, request.url)
if (request.url.match(/\.(jpg|png|gif|svg|jpeg)(\?.*)?$/)) {
return caches.match('/static/images/default.png')
} else {
// return caches.match('/offline.html')
return caches.match('./static/json/offline.json')
}
}
// 从网络请求,并将请求成功的资源缓存
function networkedAndCache (request) {
return fetch(request)
.then(response => {
const copy = response.clone()
caches.open(cacheKey('resources'))
.then(cache => {
cache.put(request, copy)
})
log('(network: cache write)', request.method, request.url)
return response
})
}
// 优先从 cache 读取,读取失败则从网络请求并缓存。网络请求也失败,则使用离线资源替代
function cachedOrNetworked (request) {
return caches.match(request)
.then((response) => {
log(response ? '(cached)' : '(network: cache miss)', request.method, request.url)
return response ||
networkedAndCache(request)
.catch(() => offlineResponse(request))
})
}
// 离线断网读取缓存:优先从 cache 读取,读取失败则使用离线资源替代
function cachedOroffline (request) {
return caches.match(request)
.then((response) => {
// debugger
log(response ? '(cached)' : '(network: cache miss)', request.method, request.url)
return response || offlineResponse(request)
})
// .catch(() => offlineResponse(request))
}
// 优先从网络请求,请求成功则加入缓存,请求失败则用缓存,再失败则使用离线资源替代
function networkedOrcachedOrOffline (request) {
return fetch(request)
.then(response => {
const copy = response.clone()
caches.open(cacheKey('resources'))
.then(cache => {
cache.put(request, copy)
})
log('(network)', request.method, request.url)
return response
})
.catch(() => cachedOroffline(request))
}
function onFetch (event) {
const request = event.request
// 优先从网络请求,请求失败则用缓存,再失败则使用离线资源替代
log(shouldAlwaysFetch(request))
if (shouldAlwaysFetch(request)) {
log('AlwaysFetch request: ', event.request.url)
event.respondWith(networkedOrcachedOrOffline(request))
return
}
// 应当从网络请求并缓存的资源
// 如果请求失败,则尝试从缓存读取,读取失败则使用离线资源替代
// // debugger
log(shouldFetchAndCache(request))
if (shouldFetchAndCache(request)) {
event.respondWith(
networkedAndCache(request).catch(() => cachedOrOffline(request))
)
return
}
// 优先从 cache 读取,读取失败则从网络请求并缓存。网络请求也失败,则使用离线资源替代
event.respondWith(cachedOrNetworked(request))
}
/**
* Activate
*/
function removeOldCache () {
return caches
.keys()
.then(keys =>
Promise.all( // 等待所有旧的资源都清理完成
keys
.filter(key => !key.startsWith(version)) // 过滤不需要删除的资源
.map(key => caches.delete(key)) // 删除旧版本资源,返回为 Promise 对象
)
)
.then(() => {
log('removeOldCache completed.')
})
}
function onActivate (event) {
log('activate event in progress.')
event.waitUntil(Promise.all([
// 更新客户端
self.clients.claim(),
removeOldCache()
]))
}
log('Hello from ServiceWorker land!', version)
self.addEventListener('install', onInstall)
self.addEventListener('fetch', onFetch)
self.addEventListener('activate', onActivate)
// self.addEventListener('push', onPush)
// self.addEventListener('sync', onSync)
// self.addEventListener('message', onMessage)
// self.addEventListener('offline', offline)
self.addEventListener('notificationclick', onClickNotify)
offlineResources 这里是 serviceWorker 注册时 需要缓存的静态文件
offline.json 文件:当用户在客户端离线第一次访问的时候,API 会返回这个json,然后在vue项目中进行相应的操作
{
"code": "offline",
"msg": "无网络连接"
}
// http响应拦截器
axios.interceptors.response.use(data => { // 响应成功关闭loading
if (data.data.code === 'offline') {
store.commit('UPDATE_LOADING', false)
store.commit('UPDATE_OfflineShow', true)
} else {
let code = Number(data.data.code)
switch (code) {
case 5000:
store.commit('SHOWTOAST', '服务器内部处理异常')
break
case 404:
store.commit('SHOWTOAST', '该请求没有找到指定资源')
break
default:
return data
}
}
}
offline.vue 期望在离线时请求接口并且还没有缓存的时候显示这个页面
上面我们已经写好了自己的service-worker.js 和 整理好了对应的离线文件
我们需要整理在开发或者放上生产的的时候,如何把service-worker.js文件复制到对应的路径当中
我们新建:regisger-service-worker.js 并且在 webpack.base.conf.js 中进行如下配置
(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('sw.js', {scope: '/'})
.then(function () { console.log('Service Worker Registered') })
}
})()
接下来在webpack.dev.conf.js和webpack.prod.conf.js 分别复制service-worker.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
new CopyWebpackPlugin([
{
from: 'service-worker.js',
to: 'sw.js'
}
])
]
进行进行上面的配置之后,你可能需要重新启动一下 npm run dev 我们来看一下:
跑的时候结果:或者你可以优化一下 在开发阶段先把regisger-service-worker隐藏掉,数据也能正常缓存,离线文件也能正常访问
bulid完之后:一样可以
现在service-worker已经完美的融合在vue项目当中了,并且能正常缓存静态文件和api数据,该例子还还封装了offline.vue文件,用户在离线的情况下访问未缓存过的api或者静态文件的时候,显示这个组件。