Skip to content

本节内容是笔者阅读了下 vite 的部分源码,又结合参考文章梳理形成。个人理解,仅供参阅。

当某一文件更新时,HMR 的完整流程如下:

  1. node server 服务器利用 chokidar 监听到目标文件更改
  2. 根据目标文件更改,根据传播边界检测热更新模块;
  3. 获取到热更新涉及到的模块后,利用 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": []
    }
  ]
}
  1. 客户端接收到通知后,根据上述数据,进行队列更新,其原理是利用 import 懒加载新的文件
  2. 当加载完成新文件后,会执行上一次加载已经注册的import.meta.disposeimport.meta.accept回调函数。
  3. 目前 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 大致做了以下几种功能:

  1. 建立 websocket 以实现客户端和服务器的实时通信;
  2. 创建 hmrClient 热更新客户端,根据服务器推送数据,import 懒加载新文件。

生命周期与操作

HMR API

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 Propagation

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 内部会触发视图层数据更新。

TIP

store 内修改某一 state 值并不会触发热更新,但新增或删除某一 state 值,则会触发热更新。

关于此特性,详见#843