思路
- 前端项目自动化部署发版本时,使用webpack构建命令生成json文件,json中写一个随机生成的字符串(比如打包版本时的时间戳),每次打包自动更新这个文件。
- 在项目中,通过定时任务或者切换页面路由,请求json文件。使用本地保存的上一次生成的字符串和json文件中的字符串进行比较,如果两个字符串不一样,则说明前端重新部署了,提醒用户进行更新操作。
实现
1. version.js
项目根目录下创建version.js
js
const fs = require('fs')
const Timestamp = new Date().getTime()
fs.writeFile('public/version.json', '{"version":' + Timestamp + '}\n', function(err) {
if (err) {
return console.log(err)
}
})
2. package.json
修改构建命令,打包时执行version.js的命令
js
"scripts": {
"build:prod": "node version.js && vue-cli-service build --mode production && rm -rf dist/static/js/*.map",
"build:staging": "node version.js && vue-cli-service build --mode staging",
},
3. vue.config.js
添加配置,通过 html-webpack-plugin 插件自定义html注入变量
js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const bpmVersion = require('./public/version.json')
...
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src')
}
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
inject: true,
bpmVersion: bpmVersion?.version
})
]
},
npm run build 打包 runtime.js 404问题
注释这一段代码: inline: /runtime..*.js$/
js
config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
// inline: /runtime\..*\.js$/
}]).end()
4. index.html
利用meta标签,存储版本号字段
npm run build 打包报错: Template execution failed: ReferenceError: BASE_URL is not defined
方法:把 BASE_URL 改成 htmlWebpackPlugin 注入字段就可以了
js
<meta name="version" content="<%= htmlWebpackPlugin.options.bpmVersion %>" />
// <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<link rel="icon" href="<%= htmlWebpackPlugin.options.url %>favicon.ico" />
5. 新建src/utils/watchUpdate.js文件
js
import Modal from '@/components/UpdateModal'
import Vue from 'vue'
let time = 0
let timer = null
let isMove = false
let isMoveTimer = null
const timerFunction = async() => {
// Stop polling when the number of times exceeds
if (isMove || time >= 1) {
clearInterval(timer)
return (timer = null)
}
const res = await fetch(`/version.json?v=${new Date().getTime()}`, {
headers: { 'Cache-Control': 'no-cache' }
})
.then(res => {
return res?.json()
})
.catch(err => {
console.log(err)
return clearTimer()
})
const latestVersion = res?.version
const currentPageVersion = Number(
document.querySelector('meta[name="version"]').content || ''
)
console.log(latestVersion, '--------', currentPageVersion)
if (latestVersion && latestVersion !== currentPageVersion) {
const MessageConstructor = Vue.extend(Modal)
const instance = new MessageConstructor({
data: {}
})
instance.id = new Date().getTime().toString()
instance.$mount()
document.body.appendChild(instance.$el)
return clearTimer()
}
time++
}
const moveFunction = () => {
time = 0
isMove = true
clearTimeout(isMoveTimer)
isMoveTimer = setTimeout(function() {
isMove = false
console.log(isMove)
if (!timer && !isMove) {
timer = setInterval(timerFunction, 1000)
}
}, 10000)
}
if (process.env.NODE_ENV !== 'development') {
timer = setInterval(timerFunction, 5000)
window.addEventListener('mousemove', moveFunction)
}
const clearTimer = () => {
clearInterval(timer)
window.removeEventListener('mousemove', moveFunction)
timer = null
}
6. 新建 updateModal.vue文件
js
<template>
<div class="update-modal-container">
<div class="update-modal">
<div class="update-modal_title">System Update 🚀</div>
<div class="update-modal_body">
The system has been updated, please refresh the page (please save the
current page data before refreshing).
</div>
<div class="update-modal_footer">
<el-button
type="primary"
size="small"
plain
@click="handleAfterLeave"
>Ignore</el-button>
<el-button
type="primary"
size="small"
@click="refresh"
>Refresh</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UpdateModal',
data() {
return {}
},
methods: {
handleAfterLeave() {
this.$destroy(true)
this.$el.parentNode.removeChild(this.$el)
},
refresh() {
this.handleAfterLeave()
location.reload(true)
}
}
}
</script>
<style lang="scss" scoped>
.update-modal-container {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
.update-modal {
user-select: none;
max-width: 450px;
background-color: #fff;
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);
border-radius: 10px;
animation: shakeY 1.5s linear;
z-index: 99998;
&_title {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f2;
color: #171725;
font-size: 20px;
font-weight: 600;
line-height: 150%;
letter-spacing: 0.1px;
}
&_body {
padding: 32px 24px;
color: #171725;
font-size: 16px;
font-weight: 400;
line-height: 150%;
letter-spacing: 0.1px;
}
&_footer {
padding: 16px 24px;
border-top: 1px solid #efefef;
display: flex;
justify-content: flex-end;
}
}
}
@keyframes shakeY {
from,
to {
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
transform: translate3d(0, -10px, 0);
}
20%,
40%,
60%,
80% {
transform: translate3d(0, 10px, 0);
}
}
.shakeY {
animation-name: shakeY;
}
</style>
7. 在main.js中导入watchUpdate文件
js
import '@/utils/watchUpdate'