前端自动检测更新

前言

这篇文章主要给大家介绍一下如何实现vue构建部署之后重新部署如何提示用户刷新更新页面。
项目地址

一、实现方案

  1. 每次打包的时候生成一个根据事件戳生成的一个时间戳文件
  2. 在主页面中不断请求这个文件,比较两次的内容是否一样,如果不一样则表示需要更新,否则不需要更新
  3. 由于需要轮询,所以将这个方法放在webworker中执行

二、实现步骤

1.编写打包插件,生成时间戳文件

import fs from 'fs'
import path from 'path'
const random = (config: any) => {
  const getARandomNumber = () => {
    console.log('执行')
    return new Date().getTime()
  }
  let randomNumber: any = null
  ;('')
  return {
    name: 'vite-plugin-vue-random',
    enforce: 'post' as 'post',
    buildStart() {
      randomNumber = getARandomNumber()
    },
    handleHotUpdate({ server, modules, timestamp }) {
      console.log(modules, timestamp)
    },
    writeBundle() {
      console.log(config.command, 'commadm')
      if (config.command === 'build') {
        // 如果没有dist文件夹,则创建
        const outputDir = path.resolve(process.cwd(), 'dist')
        if (!fs.existsSync(outputDir)) {
          fs.mkdirSync(outputDir)
        }
        console.log(randomNumber, '打包结束')
        const outputFilePath = path.join(outputDir, 'random.txt')

        // 将这个数据写入到 dist 目录中的 random.txt 文件
        try {
          fs.writeFileSync(outputFilePath, randomNumber.toString())
          console.log(randomNumber, '获取的随机数,已写入到', outputFilePath)
        } catch (e) {
          console.error(e)
        }
      }
    },
    // 打包结束
    buildEnd() {},
  }
}

export { random }

然后在vite.config.ts中引入该文件,在plugins中配置

 plugins: [
      vue(),
      vueJsx(),
      vueDevTools(),
      random(config),
    ],

2.编写轮询

App.vue

<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
import { onMounted } from 'vue'
import { Button } from 'vant'

let worker: Worker | null = null
// let oldHtml = ''
onMounted(() => {
  console.log(213)
  // 初始化执行
  workerFun()
  // 获取加载的js文件
  // 如果是生产环境
  // 监听当前页面是否可见

  document.addEventListener('visibilitychange', () => {
    // 如果可见
    worker?.terminate()
    worker = null
    console.log(document.visibilityState, '1111111')
    if (document.visibilityState === 'visible') {
      console.log('页面可见')
      workerFun(false)
    } else {
      worker = null
    }
  })
})
const workerFun = (ischange = false) => {
  if (import.meta.env.MODE === 'production') {
    worker = new Worker(new URL('./works/updateSource.ts', import.meta.url))
    let oldHtml = localStorage.getItem('oldHtml') || ''
    worker.postMessage({ type: 'update', oldHtml })
    worker.onmessage = (e) => {
      if (e.data.message === 1) {
        if (confirm('是否更新资源')) {
          location.reload()
          // oldHtml = e.data.newHtml
          localStorage.setItem('oldHtml', e.data.newHtml)
          // window.location.reload(true) 从服务器重新加载
        }
      }
    }
  }
}
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />

      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
      </nav>
      <div class="test">test 数据2</div>
      <Button type="success">主要按钮</Button>
    </div>
  </header>

  <RouterView />
</template>

<style scoped>
.test {
  width: 120px;
  font-size: 25px;
  height: 40px;
}
header {
  line-height: 1.5;
  max-height: 100vh;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

nav {
  width: 100%;
  font-size: 12px;
  text-align: center;
  margin-top: 2rem;
}

nav a.router-link-exact-active {
  color: var(--color-text);
}

nav a.router-link-exact-active:hover {
  background-color: transparent;
}

nav a {
  display: inline-block;
  padding: 0 1rem;
  border-left: 1px solid var(--color-border);
}

nav a:first-of-type {
  border: 0;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }

  nav {
    text-align: left;
    margin-left: -1rem;
    font-size: 1rem;

    padding: 1rem 0;
    margin-top: 1rem;
  }
}
</style>

updateSource.ts内容

let timeInterval: any = null
// let html = ''
let html = ''
// 请求获取当前页面
async function updateSource() {
  // 加上时间戳,防止缓存
  //   const url = `/?t=${Date.now()}`
  //   const res = await fetch(url)
  //   const htmls = await res.text()
  const respons = await fetch(`/random.txt?t=${Date.now()}`)
  //   console.log(await respons.text(), 'respons')
  const text = await respons.text()
  // console.log(htmls,'htmls')
  return text
}
// 判断是否需要更新
async function isUpdate() {
  const newHtml = await updateSource()
  console.log(newHtml, html, 111)
  if (newHtml !== html) {
    clearInterval(timeInterval)
    // 提示需要更新
    self.postMessage({ message: 1, newHtml })
    html = newHtml
  }
}
self.addEventListener('message', async (event: MessageEvent) => {
  let { type, oldHtml } = event.data
  if (type === 'update') {
    //    先获取第一次数据

    html = oldHtml
    oldHtml = await updateSource()

    timeInterval && clearInterval(timeInterval)
    timeInterval = setInterval(() => {
      isUpdate()
    }, 2000)
  }
})

接下来我将详细说一下代码逻辑,核心是updateSource.ts文件

  1. app.vue通知updateSource开始轮询查询数据
  2. webworker中通过message后先获取第一次的数据,然后开始进行轮询接口获取random.txt文件的数据(通过random.ts插件打包生成),比较两次是否一致
  3. 如果不一致通过postmessage通知主线程需要进行更新
  4. 主线程收到通知给出提示
    其实以上逻辑已经完全可以实现了,但是很明显,上面的代码案例不仅仅只有我描述的这些;主要是做了一些优化:在页面看不到的情况下,比如浏览器最小化或者切换到其他标签页的时候,这个轮询将会被停止,所以加上了下面的代码
 document.addEventListener('visibilitychange', () => {
    // 如果可见
    worker?.terminate()
    worker = null
    console.log(document.visibilityState, '1111111')
    if (document.visibilityState === 'visible') {
      console.log('页面可见')
      workerFun(false)
    } else {
      worker = null
    }
  })

只有当页面可见的时候才会执行webworker的代码,不过按照上述方法的话,每次会重新加载webworker的代码,会导致更新提示不准确(即便没有资源更新也会提示更新),所以每次将获取最新的random.txt内容放入缓存中,每次比较的缓存中的跟最新的,能够保证webworker重新加载之后比较正确
以下是部分代码:

const workerFun = (ischange = false) => {
  if (import.meta.env.MODE === 'production') {
    worker = new Worker(new URL('./works/updateSource.ts', import.meta.url))
    let oldHtml = localStorage.getItem('oldHtml') || ''
    worker.postMessage({ type: 'update', oldHtml })
    worker.onmessage = (e) => {
      if (e.data.message === 1) {
        if (confirm('是否更新资源')) {
          location.reload()
          // oldHtml = e.data.newHtml
          localStorage.setItem('oldHtml', e.data.newHtml)
          // window.location.reload(true) 从服务器重新加载
        }
      }
    }
  }
}
//updateSource.ts
self.addEventListener('message', async (event: MessageEvent) => {
  let { type, oldHtml } = event.data
  if (type === 'update') {
    html = oldHtml
    // oldHtml = await updateSource()
    timeInterval && clearInterval(timeInterval)
    timeInterval = setInterval(() => {
      isUpdate()
    }, 2000)
  }
})
const newHtml = await updateSource()
  console.log(newHtml, html, 111)
  //每次比较的都是缓存的数据跟最新的数据比较
  if (newHtml !== html) {
    clearInterval(timeInterval)
    // 提示需要更新 再将新数据返回主线程放入缓存中
    self.postMessage({ message: 1, newHtml })
    html = newHtml
  }

注意:当打开控制台的时候visibilitychange会有问题,即使切换到不可见,也会默认为可见状态,这会导致测试通过控制台并不容易测试,可以通过nginx配置请求日志来查看记录

server {
		# gzip on;
		# gzip_static on;
		# gzip_min_length 1k;
		# gzip_buffers 4 16k;
		# gzip_comp_level 2;
		# gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/png image/jpeg image/svg+xml image/gif;
		# gzip_vary off;
		# gzip_disable "MSIE [1-6].";

        server_name  localhost;

        #charset koi8-r;

        access_log  logs/access.log;

总结

以上就是通过轮询实现自动检测更新,但是缺点就是要不断访问数据,对服务端压力比较大,好处是之后每次更新不需要做任何配置和修改;
当然还有其他方法大家可以自行查阅资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值