前言
这篇文章主要给大家介绍一下如何实现vue构建部署之后重新部署如何提示用户刷新更新页面。
项目地址
一、实现方案
- 每次打包的时候生成一个根据事件戳生成的一个时间戳文件
- 在主页面中不断请求这个文件,比较两次的内容是否一样,如果不一样则表示需要更新,否则不需要更新
- 由于需要轮询,所以将这个方法放在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文件
- app.vue通知updateSource开始轮询查询数据
- webworker中通过message后先获取第一次的数据,然后开始进行轮询接口获取random.txt文件的数据(通过random.ts插件打包生成),比较两次是否一致
- 如果不一致通过postmessage通知主线程需要进行更新
- 主线程收到通知给出提示
其实以上逻辑已经完全可以实现了,但是很明显,上面的代码案例不仅仅只有我描述的这些;主要是做了一些优化:在页面看不到的情况下,比如浏览器最小化或者切换到其他标签页的时候,这个轮询将会被停止,所以加上了下面的代码
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;
总结
以上就是通过轮询实现自动检测更新,但是缺点就是要不断访问数据,对服务端压力比较大,好处是之后每次更新不需要做任何配置和修改;
当然还有其他方法大家可以自行查阅资料

675

被折叠的 条评论
为什么被折叠?



