本节内容是笔者阅读了下 vite
的部分源码,又结合参考文章梳理形成。个人理解,仅供参阅。
当某一文件更新时,HMR
的完整流程如下:
node server
服务器利用chokidar
监听到目标文件更改;- 根据目标文件更改,根据传播边界检测热更新模块;
- 获取到热更新涉及到的模块后,利用
socket
通知客户端,其socket
数据格式大致如下:
json
{
"type": "update",
"updates": [
{
"type": "js-update",
"timestamp": 1717489737179,
"path": "/src/store/count.js",
"acceptedPath": "/src/store/count.js",
"explicitImportRequired": false,
"isWithinCircularImport": false,
"ssrInvalidates": []
}
]
}
- 客户端接收到通知后,根据上述数据,进行队列更新,其原理是利用
import
懒加载新的文件; - 当加载完成新文件后,会执行上一次加载已经注册的import.meta.dispose和import.meta.accept回调函数。
- 目前
vite
会自动向.vue
单文件等注入如下热更新脚本:
js
// .vue单文件
import.meta.hot.accept(mod=>{
if (!mod) return
const {default: updated, _rerender_only} = mod
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
js
// .css
import.meta.hot.accept()
import.meta.hot.prune(()=>__vite__removeStyle(__vite__id))
@vite/client
vite
本地启动了 node server
,托管了 index.html
。
并且在 index.html
中注入了@vite/client:
html
<script type="module" src="/@vite/client"></script>
@vite/client
大致做了以下几种功能:
- 建立
websocket
以实现客户端和服务器的实时通信; - 创建
hmrClient
热更新客户端,根据服务器推送数据,import
懒加载新文件。
生命周期与操作
import.meta.hot.accept([deps], cb)
模块自更新时触发回调;
或者指定模块更新时触发该监听回调。
import.meta.hot.dispose(cb)
旧模块被舍弃时触发回调
import.meta.hot.decline()
该操作,传播边界依旧有效,但会停止热更新。
import.meta.hot.invalidate()
该操作,会使得已有的传播边界失效,继续向上搜索边界以更新。
传播边界
当某文件更新时,会根据依赖树查找直至 import.meta.accept
存在,而存在 import.meta.accept
的文件会作为更新入口,进行热更新加载渲染。
HMR In Pinia
当利用 Pinia
创建 store
时,默认情况下,当 store
更新时,vite
会根据传播边界热更新 SFC
文件。
虽然此过程中重新加载了新的 store
文件,但并不会触发 view
视图层的 store
数据更新。
TIP
测试发现,在此种默认情况下,即使 store
文件懒加载了,但 store
内部判断了如果是 hot
热更新,则使用已备份的 store
。
为此,我们需要在对应 store
文件中添加:
js
import { acceptHMRUpdate } from 'pinia'
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useYourStore, import.meta.hot))
}
添加完成之后,再更新 store
文件,传播边界会仅限于该文件,且 acceptHMRUpdate
内部会触发视图层数据更新。