得间免费小说最新版
108.8MB · 2025-11-07
热更新是指:在开发过程中,当代码发生修改并保存后,浏览器无需刷新整个页面,仅更新修改的模块(如组件、样式、逻辑等),同时保留页面当前状态(如表单输入、滚动位置、组件数据等) 。
与传统的 “自动刷新”(如 live-reload)相比,HMR 的核心优势是:
要实现热更新,首先需要建立开发服务器与浏览器之间的 “实时通信通道”,否则浏览器无法知道 “代码何时被修改了”。
在 Vite 中:
vite dev),Vite 会在本地启动一个 HTTP 服务器(默认端口 5173),负责提供页面资源(HTML、JS、CSS 等),同时监听文件变化。WebSocket 是一种全双工通信协议,允许客户端(浏览器)和服务器在建立连接后,双向实时发送消息(无需客户端反复请求)。这是热更新的 “通信核心”。
在 Vite 中,WebSocket 的作用是:
假设我们在开发一个 Vue 项目,修改了 src/components/Hello.vue 并保存,Vite 的热更新流程如下:
chokidar 库(文件监听工具)对项目目录(如 src/)进行监听,实时检测文件的创建、修改、删除等操作。Hello.vue 时,文件系统会触发 “修改事件”,Vite 服务器立刻感知到:src/components/Hello.vue 发生了变化。Vite 基于 “原生 ESM(ES 模块)” 工作:开发时不会打包所有文件,而是让浏览器直接通过 <script type="module"> 加载模块。
当 Hello.vue 被修改后,Vite 只会重新编译这个单文件组件(.vue 文件):
id=123)。同时,Vite 会分析 “依赖关系”:判断哪些模块依赖了 Hello.vue(比如父组件、页面等),确定需要更新的 “模块范围”。
Vite 服务器内置了 WebSocket 服务(默认路径为 ws://localhost:5173/ws),浏览器加载页面时,会自动通过 JavaScript 连接这个 WebSocket。
服务器将 “变更信息” 通过 WebSocket 发送给浏览器,信息格式类似:
{
"type": "update", // 类型:更新
"updates": [
{
"type": "js-update", // 更新类型:JS 模块
"path": "/src/components/Hello.vue", // 变更文件路径
"acceptedPath": "/src/components/Hello.vue",
"timestamp": 1699999999999 // 时间戳(避免缓存)
}
]
}
这个消息告诉浏览器:Hello.vue 模块更新了,需要处理。
浏览器的 Vite 客户端(Vite 注入的 HMR 运行时脚本)接收到 WebSocket 消息后,解析出需要更新的模块路径(Hello.vue)。
客户端通过 HTTP 请求(而非 WebSocket)向服务器获取 “更新后的模块内容”,请求地址类似:
http://localhost:5173/src/components/Hello.vue?t=1699999999999
(t 参数是时间戳,用于避免浏览器缓存旧内容)。
客户端拿到新的 Hello.vue 模块内容后,会执行 “模块替换”:
defineComponent 和热更新 API(import.meta.hot),将旧组件的实例替换为新组件的实例;data 中的数据),仅更新模板、样式或逻辑;.css),会直接替换 <style> 标签内容,无需重新渲染组件。替换完成后,Vue 的虚拟 DOM 会对比新旧节点,只更新页面中受影响的部分(如 Hello.vue 对应的 DOM 区域),实现 “局部刷新”。
main.js、路由配置、全局状态等),模块依赖关系过于复杂,无法安全地局部更新。location.reload(),确保代码更新生效。import 语句),修改一个模块时,只通知依赖它的模块更新,范围最小化。@vitejs/plugin-vue 插件),确保组件状态正确保留。文件修改(保存)
↓
Vite 服务器监听文件变化
↓
编译变更模块(仅修改的文件)
↓
WebSocket 发送更新通知(告诉浏览器“哪个模块变了”)
↓
浏览器通过 HTTP 请求新模块内容
↓
替换旧模块,框架(如 Vue)局部更新视图
↓
页面更新完成(状态保留,无需全量刷新)
src/App.vue 并保存当你运行 vite dev 时,Vite 会同时启动两个服务:
http://localhost:5173,负责给浏览器提供页面、JS、CSS 等资源(比如你在浏览器输入这个地址就能看到项目)。ws://localhost:5173/ws,专门用来和浏览器 “实时聊天”(双向通信)。浏览器打开项目页面时,会自动通过一段 Vite 注入的 JS 代码,连接这个 WebSocket(相当于浏览器和服务器之间架了一根 “实时电话线”)。
你修改 App.vue 并按 Ctrl+S 保存:
App.vue 变了。App.vue 这个文件变了”。然后,Vite 通过 WebSocket 给浏览器发一条消息(就像打电话通知):
{
"type": "update",
"updates": [{"path": "/src/App.vue", "timestamp": 123456}]
}
翻译成人话:“喂,浏览器!src/App.vue 这个文件刚刚改了,赶紧处理一下!”
浏览器接收到 WebSocket 的消息后,知道了 “App.vue 变了”,但此时它还没有新内容。
于是浏览器会通过 HTTP 协议,向 Vite 的 HTTP 服务发一个请求,要新的 App.vue 内容:
GET http://localhost:5173/src/App.vue?t=123456
(t=123456 是时间戳,确保拿到的是最新的,不是缓存的旧内容)。
Vite 的 HTTP 服务收到请求后,把刚才处理好的 “更新后的 App.vue 内容” 返回给浏览器。
浏览器收到新的 App.vue 代码后,会:
App.vue 模块(但保留组件状态,比如输入框里的文字)。App.vue 对应的页面部分(不会刷新整个页面)。最终你看到的效果:页面上只有 App.vue 相关的部分变了,其他内容和状态都没变。
你改了文件 → Vite 发现 → 用 WebSocket 通知浏览器“哪个文件变了” → 浏览器用 HTTP 要这个文件的新内容 → 浏览器局部更新页面
WebSocket 只负责 “传递变化的消息”(谁变了),真正的新内容还是通过 HTTP 请求获取 —— 分工明确,效率更高。这也是 Vite 热更新快的原因之一:只传必要的消息,拿必要的新内容,不做多余的事。
这个问题很关键!核心原因是: “直接传变化内容” 看似省一步,实际会引发更复杂的问题,反而降低效率。Vite 选择 “WebSocket 传通知 + HTTP 取内容” 的分工,是权衡了前端开发的复杂性后的最优解。
前端开发中,一个文件的修改可能涉及大量内容(比如一个复杂的 Vue 组件、包含数百行 CSS 的样式文件)。
WebSocket 虽然支持二进制传输,但设计初衷是 “轻量实时通信”(比如消息通知、状态同步),并不擅长高效传输大体积的代码内容。
如果直接通过 WebSocket 传完整的更新内容,会:
?t=时间戳 可以轻松避免缓存(确保拿到最新内容),而 WebSocket 消息没有内置的缓存机制,需要手动处理。<script type="module"> 加载 ES 模块(Vite 开发时的核心机制),而模块加载天然依赖 HTTP 请求。直接用 WebSocket 传代码,还需要手动模拟模块加载逻辑,反而更复杂。WebSocket 就像 “短信通知”:店家(服务器)告诉你 “你点的餐好了”(哪个文件变了),短信内容很短,效率高。
HTTP 请求就像 “去取餐”:你收到通知后,自己去店里(服务器)拿餐(新内容),按需行动。
如果店家直接 “把餐扔到你家”(WebSocket 传内容),可能会出现:
Vite 之所以让 WebSocket 只传 “通知”、让 HTTP 负责 “传内容”,是因为:
这种设计看似多了一次 HTTP 请求,实则通过 “各司其职” 让整个热更新流程更稳定、更高效 —— 这也是 Vite 热更新速度远超传统工具的原因之一。