如何把vue项目改造成支持PWA的功能(第二章:加入service-worker)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012878818/article/details/85236592

第一章中,我们加入Manifest ,让我们的网站生产图标放在手机桌面上,入口更加的明显了,和native差不多,大大的提升了用户体验,免去用户收藏网址等操作。

这章中,我们将加入PWA的核心:service-worker

相关代码:https://github.com/hwq888/yeye-PWA

1、什么是Service Worker?

下面简单的介绍一下,本章主要说怎么和VUE结合使用

service worker 是一个 WEB API,是 Web Workers 的一种实现,功能是可以拦截、处理请求,配合 CacheStorage 等 api,可以做到将资源存储到本地等,实现离线访问。

2、生命周期

service workers 的生命周期有:

  • 安装 install
  • 激活 activate

3、新建文件service-worker.js

'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  期望在离线时请求接口并且还没有缓存的时候显示这个页面

<!--无网络连接-->
<template>
  <div>
    <div class="abs offline_box bg_white">
      <div class="nofind_box tc flex_column flex_center">
        <img class="nofind_ico" :src="offlineIco"/>
        <p class="nofind_title">无网络连接</p>
        <p class="nofind_text">您当前没有网络连接,部分网页使用了离线访问技术,您还可以继续访问!</p>
        <div class="btn_s_box mb50 mt20">
          <a class="small_btn" @click="goBackIndex">返回首页</a>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        offlineIco: './static/images/default.png'
      }
    },
    methods: {
      goBackIndex () {
        this.$store.commit('UPDATE_OfflineShow', false)
        this.$router.replace('/')
      }
    }
  }
</script>
<style lang="less" scoped>
  .offline_box{ position: fixed; left: 0; right: 0;bottom:0; top:0;}
  .nofind_box {height: 100vh;}
  .nofind_ico{ width: 60%; height: auto; margin-top: -1rem;}
  .nofind_title{ font-size: 0.32rem; color: #999; margin-top: 0.2rem;}
  .nofind_text{ font-size: 0.28rem; color: #C4CDD7; margin-top: 0.2rem; padding: 0 0.5rem;}
  .btn_s_box .small_btn{
    background-image: linear-gradient(-180deg, #ff9d32 0%, #ff682a 100%);
    box-shadow: 0 10px 15px #FFD9CF;
    border-radius: 0.5rem;
    font-size: 0.24rem!important; padding:0.1rem 0.5rem; color: #fff;
  }
</style>

4、如何把service-worker.js加入到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或者静态文件的时候,显示这个组件。

猜你喜欢

转载自blog.csdn.net/u012878818/article/details/85236592
今日推荐