记录---前端怎么自动检测代码更新?
<h1 data-id="heading-0">🧑💻 写在开头</h1><p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<div>
<div>
<p>在前端开发中,我们通常会使用打包工具(如 Vite、Webpack)将 JS/CSS 等资源构建上线,并配合 CDN、浏览器缓存来加速访问。但问题也随之而来:</p>
<blockquote>
<p>“我已经发布新版本了,怎么用户还看到旧界面?”</p>
<p>“为啥修复的 bug 还在?”</p>
</blockquote>
<p>原因很简单:<strong>用户浏览器缓存了旧的静态资源,没有感知新版本的变更。</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202505/2149129-20250513172149775-198727142.png" alt="" loading="lazy"></p>
</div>
<div>
<div>
<p>🎯 目标:前端页面如何<strong>自动检测打包更新并提示刷新</strong></p>
<p>关键需求是:</p>
<ul>
<li>服务器发布了新版本,客户端能“感知”到。</li>
<li>最好不依赖后端数据库或额外接口。</li>
<li>弹窗或强制刷新,提示用户更新。</li>
</ul>
<h3 data-id="heading-0">思考:为啥不直接加 hash?</h3>
<p>其实我们打包时已经使用 <code></code> 命名资源了,那为啥还要检测更新?</p>
<p>因为:</p>
<ul>
<li>浏览器对 HTML 本身不会缓存 bust;</li>
<li>页面里写死了旧的 <code><script src="/main.abcd.js"></code>,不会自动拉新版;</li>
<li>用户长时间不刷新页面就不会拉取新 HTML,自然也没拉到新版 JS。</li>
</ul>
<p>所以必须在 <strong>运行中的前端代码层面</strong> 进行更新感知。</p>
<h3 data-id="heading-1">方案一:轮询版本文件(最常用)</h3>
<h4 data-id="heading-2">实现思路:</h4>
<ol>
<li>打包时生成一个版本文件(如 <code>version.json</code> 或注入 <code>index.html</code> 里的 <code>__BUILD_VERSION__</code> 字段);</li>
<li>页面加载后,定时轮询该文件;</li>
<li>一旦<strong>检测到版本变更(比如哈希不同)</strong>,提示用户刷新。</li>
</ol>
<p>示例代码:</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">const CURRENT_VERSION = __BUILD_VERSION__; // 构建时注入变量
setInterval(() => {
fetch('/version.json?t=' + Date.now())
.then(res => res.json())
.then(({ version }) => {
if (version !== CURRENT_VERSION) {
alert('检测到新版本,点击确定刷新页面');
window.location.reload(true);
}
});
}, 10000); // 每 10 秒轮询一次</pre>
</div>
<div>
<div>
<p>或者使用<strong>文件指纹</strong>思路:</p>
<ol>
<li>
<p><strong>打包后生成一个 manifest 文件</strong>(比如 <code>asset-manifest.json</code> 或 <code>meta.json</code>),里面记录资源及其 hash;</p>
</li>
<li>
<p>页面运行时轮询这个 manifest 文件;</p>
</li>
<li>
<p>如果发现文件名或 hash 有变,提示刷新页面。</p>
</li>
</ol>
<p>在 Vite 中添加如下配置:</p>
</div>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
manifest: true, // 开启 manifest 生成
}
})
</pre>
</div>
<p> </p>
构建后会生成 <code>dist/manifest.json</code>,类似这样:<br>
</div>
<div> </div>
<div>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">{
"index.html": { "file": "index.html", "isEntry": true },
"src/main.ts": { "file": "assets/main.abc123.js", "isEntry": true },
"src/style.css": { "file": "assets/style.def456.css" }
}
</pre>
</div>
<p> </p>
</div>
</div>
<p>前端代码(可封装成检测模块):</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">let currentVersionHash = ''
function getHashFromManifest(manifest: any): string {
// 自定义 hash 生成方式:可拼接所有文件路径,也可只取主入口
return Object.values(manifest)
.map((entry: any) => entry.file)
.join('|')
}
async function checkForUpdate() {
try {
const res = await fetch(`/manifest.json?_t=${Date.now()}`)
const manifest = await res.json()
const newHash = getHashFromManifest(manifest)
if (!currentVersionHash) {
currentVersionHash = newHash
} else if (currentVersionHash !== newHash) {
console.log('🚨 发现新版本,准备刷新页面')
alert('检测到新版本,点击确认刷新页面')
window.location.reload(true)
}
} catch (err) {
console.error('版本检查失败', err)
}
}
setInterval(checkForUpdate, 10000) // 每 5 秒检查一次
</pre>
</div>
<p> </p>
<div>
<div>
<blockquote>
<p>💡<code>manifest.json</code> 中的文件路径含 hash,只要内容变,hash 就会变,就能准确识别版本是否更新。</p>
</blockquote>
<h4 data-id="heading-3">优点:</h4>
<ul>
<li>实现简单,支持所有现代浏览器;</li>
<li>可手动控制提示逻辑,提升用户体验;</li>
<li>和打包工具(Vite/Webpack)配合紧密。</li>
</ul>
<h4 data-id="heading-4">缺点:</h4>
<ul>
<li>本质上还是轮询,数据拉取有频率;</li>
<li>如果用户挂着页面一整天,刷新前所有交互还是旧代码。</li>
</ul>
<h3 data-id="heading-5">方案二:WebSocket(配合 CI/CD 或打包系统)</h3>
<p>✅ 前提:</p>
<ul>
<li>服务端拥有 WebSocket 服务能力(如 Node.js + <code>ws</code> 模块);</li>
<li>每次前端构建部署完成后,触发一次“更新通知”。</li>
</ul>
<p>服务端(nodejs+ws模块):</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">// ws-server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
console.log('🚀 New client connected');
clients.add(ws);
ws.on('close', () => {
clients.delete(ws);
});
});
// 通知所有客户端刷新页面
function notifyClients() {
for (let client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'update', message: 'new-version' }));
}
}
}
// 模拟触发更新(实际应在构建完成后调用)
setTimeout(() => {
console.log('🎉 New version released, notifying clients...');
notifyClients();
}, 10000); // 10 秒后触发</pre>
</div>
<blockquote>
<p>如果你有配套的部署平台或打包平台(如 Jenkins、Vercel、Netlify),可以在部署成功后广播一条“版本已更新”的消息。</p>
</blockquote>
<p>客户端建立 WebSocket 连接,一旦收到推送,就提示刷新页面。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">if ('WebSocket' in window) {
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log(' Connected to update server');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'update') {
console.log(' New version detected');
// 这里可以是弹窗提醒,或者直接刷新
if (confirm('检测到新版本,是否立即刷新页面?')) {
window.location.reload();
}
}
};
ws.onclose = () => {
console.log(' Connection closed');
};
ws.onerror = (err) => {
console.error(' Error:', err);
};
}</pre>
</div>
<div>
<div>
<blockquote>
<p>可封装为插件或独立模块进行复用,比如 Vue 插件、React Hook、Vite 插件等。</p>
</blockquote>
<h4 data-id="heading-6">优点:</h4>
<ul>
<li>实时;</li>
<li>可以配合更多更新逻辑(如强制下线);</li>
</ul>
<h4 data-id="heading-7">缺点:</h4>
<ul>
<li>成本高,依赖后端支持,对于检测更新的需求一般无需这么实时;</li>
<li>不适合资源有限的小项目;</li>
</ul>
<h3 data-id="heading-8">总结</h3>
<p>前端页面检测更新并不是“有没有后端通知”,而是我们<strong>主动地检测自己的版本是否已经被 CDN 或打包平台更新</strong>。 <strong>轮询 <code>version.json</code></strong> 是最稳定、兼容性最强的方案; <strong>WebSocket 通知</strong> 是顶配方案,适合重型系统。</p>
</div>
<div>
<h2>本文转载于:https://juejin.cn/post/7489755022655291432</h2>
</div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></p>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/18874571
頁:
[1]