背景
最近联调接口时,后端偶尔不刷新页面就调试接口,时不时需要提醒她刷页面再操作。于是想到了版本更新这个功能来自动完成这个事。
功能实现
- 使用 vite 插件构建 git 提交信息来生成版本信息文件
- 将版本信息在 window 存一份,方便后续比较
- 使用事件操作来对比版本文件信息是否一致
- 既然是提示,我们不强制一定更新
生成 vite 插件
下列是我所有项目必备的一个插件,会在根目录生产一个资产文件和一个版本信息文件
import { execSync } from 'child_process'
import os from 'os'
const platform = os.platform()
let code = 0
export default function VitePluginBuildLegacy() {
return {
name: 'vite-plugin-build-legacy',
apply: 'build',
generateBundle(_options, bundle) {
let content = `| 文件名 | 大小 | \n| :---: | --- | \n`
const [assets, chunks] = Object.values(bundle).reduce(
(r, i) => (r[i.type === 'asset' ? 0 : 1].push(i), r),
[[], []]
)
assets.forEach(
(item) => (content += `| ${item.fileName} | ${formatSize(item.source.length)} | \n`)
)
chunks.forEach(
(item) => (content += `| ${item.fileName} | ${formatSize(item.code.length)} | \n`)
)
const totalSize =
assets.reduce((total, item) => total + item.source.length, 0) +
chunks.reduce((total, item) => total + item.code.length, 0)
content += `| 总计 | ${formatSize(totalSize)}(仅构建资产) | \n`
this.emitFile({ type: 'asset', fileName: 'assets.md', source: content })
this.emitFile({
type: 'asset',
fileName: 'version.json',
source: JSON.stringify(GeneratVersion(), null, 2),
})
},
}
}
function formatSize(size) {
if (size < 1024) {
return size + 'B'
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + 'KB'
} else if (size < 1024 * 1024 * 1024) {
return (size / (1024 * 1024)).toFixed(2) + 'MB'
} else {
return (size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
}
}
export function GeneratVersion(assets_dir) {
try {
const commitId = execSync('git log -n1 --format=format:"%H"').toString().trim()
const author = execSync('git log -n1 --format=format:"%an"').toString().trim()
let branch
try {
branch = execSync('git symbolic-ref --short HEAD').toString().trim()
} catch (error) {
branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim()
}
const commitTime = execSync('git log -n1 --format=format:"%ad" --date=iso')
.toString()
.substring(0, 19)
const content = execSync('git log -n1 --format=format:"%s"').toString().trim()
const json = { platform, commitId, author, branch, commitTime, content, code }
assets_dir && fs.writeFileSync(assets_dir, JSON.stringify(json, null, 2))
return json
} catch (error) {
return { msg: '获取版本信息失败', error: error.message }
}
}
插件注册
vite.config.js 代码注册使用
import VitePluginBuildLegacy, { GeneratVersion } from './vite-plugin-build-legacy'
export default defineConfig(({ command, mode }) => {
return {
// ... your code
define: {
__version__: JSON.stringify(GeneratVersion()),
},
plugins: [
// ... your code
VitePluginBuildLegacy(),
]
}
})
现在我们执行构建项目时候根目录会生成一个version.json(信息如下:),之所以是 json 信息,因为便于处理。
{
"platform": "darwin",
"commitId": "27c13a818c9745bf66cef63f67be4ed684341049",
"author": "****",
"branch": "master",
"commitTime": "2024-10-31 15:34:41",
"content": "refactor: 优化记忆模块排序和计数逻辑"
}
浏览器版本更新事件
- 我没采用轮巡的是不想看到一堆无效的请求出现,而且这个功能和后端几乎不挨边。
- 由于处理的东西可能较多,我加使用 class构造类 来完成,这样代码代码逻辑会清晰,功能明确一些。
功能说明
- 方案采取页面激活(visibilitychange)来比较版本信息
- 使用页面聚焦(focus)来比较版本信息
- 可以不强制用户一定刷新页面,需要配置取消操作,怕用户正进行一些表单的操作或者重要信息的操作。
- 万变不离其宗,目的就是为了完成页面刷新。下列代码在 main 里面调用一下就完成了。
import { ElMessageBox } from 'element-plus'
const isDev = import.meta.env.MODE === 'development'
const oneMinutes = 1 * 60 * 1000
const versioned = (url) =>
new Promise((resolve, reject) => {
fetch(`${url}?_=${Date.now()}`)
.then((res) => res.json())
.then(resolve)
.catch(reject)
})
export class RefreshBrowserScript {
url = '/version.json'
version = window.__version__
status = false
now = 0
constructor() {
if (isDev) return
window.addEventListener('focus', (this.__focus__fn__ = this.focus.bind(this)))
document.addEventListener(
'visibilitychange',
(this.__visibilitychange__fn__ = this.visibilitychange.bind(this))
)
this.execute()
}
visibilitychange() {
if (document.visibilityState !== 'visible') return
this.execute()
}
focus() {
if (this.now + oneMinutes >= Date.now()) return
this.now = Date.now()
this.execute()
}
async execute() {
if (this.status) return
const version = await this.fetchData()
const samecase = this.equalVersion(this.version, version)
if (samecase) return
this.throwMessage()
this.bindEvent()
this.status = true
}
fetchData() {
return versioned(this.url)
}
bindEvent() {
document.addEventListener('keypress', this.refreshBrowser)
}
unbingEvent() {
document.removeEventListener('keypress', this.refreshBrowser)
}
throwMessage() {
return ElMessageBox.alert('发现新版本,请刷新浏览器(按任意键刷新页面)。', '版本提示', {
type: 'warning',
showCancelButton: true,
confirmButtonText: '刷新页面',
cancelButtonText: '不刷新,继续留在当前页面',
})
.then(this.refreshBrowser)
.catch(() => {
this.skipRefresh()
this.unbingEvent()
})
}
equalVersion(privite, resource) {
return privite.commitId === resource.commitId
// return privite.code === resource.code // 不使用 commit hash,使用randomString
}
skipRefresh() {
window.removeEventListener('focus', this.__focus__fn__)
document.removeEventListener('visibilitychange', this.__visibilitychange__fn__)
this.status = false
}
refreshBrowser() {
window.location.reload()
}
}