热更新原理
从整体角度来看,vite 热更新主要分为三步
创建模块依赖图:服务启动时创建 ModuleGraph 实例,执行 transform 钩子时创建 ModuleNode 实例,记录模块间的依赖关系
服务端收集更新模块:服务启动时通过 chokidar 创建监听器,当文件发生变化时收集需要热更新的模块,将需要更新的模块信息通过 websocket 发送给客户端
客户端派发更新:服务器启动时会在 index.html 注入一段客户端代码,创建一个 websocket 服务监听服务端端发送的热更新信息,在收到服务端的信息后根据模块依赖关系进行模块热更新
创建模块依赖图
在 vite 中,主要通过 ModuleGraph 和 ModuleNode 来建立各模块依赖关系,ModuleGraph 记录模块及模块的所有依赖,ModuleNode 记录模块节点具体信息
模块依赖图在项目启动时通过 ModuleGraph 类创建一个实例
const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
container.resolveId(url, undefined, { ssr })
);ModuleGraph 主要通过三个 Map 和一个 Set 来记录模块信息,包括
urlToModuleMap:原始请求 url 到模块节点的映射,如 /src/index.tsx(vite 中的每个模块 url 是唯一的)
idToModuleMap:模块 id 到模块节点的映射,id 是原始请求 url 经过 resolveId 钩子解析后的结果
fileToModulesMap:文件到模块节点的映射,由于单文件可能包含多个模块,如 .vue 文件,因此 Map 的 value 值为一个集合
safeModulesPath:记录被认为是“安全”的模块路径,安全路径不需要模块转换和处理
// 目录:packages/vite/src/node/server/moduleGraph.ts
export class ModuleGraph {
urlToModuleMap = new Map<string, ModuleNode>()
idToModuleMap = new Map<string, ModuleNode>()
fileToModulesMap = new Map<string, Set<ModuleNode>>()
safeModulesPath = new Set<string>()
}ModuleGraph 三个 map 中存储的就是 ModuleNode 模块节点的信息,ModuleNode 中记录了三个和热更新相关的重要属性
importers:当前模块被哪些模块引用
clientImportedModules:当前模块依赖的其他模块
acceptedHmrDeps:其他模块对当前模块的依赖关系,发生热更新时,根据 acceptedHmrDeps 记录的信息通知其他模块信息热更新
服务端收集更新模块
在服务启动阶段,使用 chokidar 的 watch 方法创建文件监听器,监听文件的修改、新增、删除操作
当文件修改时,有三个执行步骤
获取到标准的文件路径
通过 moduleGraph 实例的 onFileChange 方法移除文件缓存信息
执行热更新方法 onHMRUpdate
对于文件的新增和删除,使用的同一个方法,执行步骤和文件修改类似,只是第二步的方法有所不同,但本质上都是使用 moduleGraph 的 onFileChange 方法移除文件缓存信息,再执行热更新方法 onHMRUpdate
在服务启动阶段,会通过 chokidar 的 watch 方法方法创建一个文件监听器,当文件发生修改、新增和删除操作时,执行热更新操作
热更新操作前会调用 moduleGraph 实例的 onFileChange 方法,清理文件的缓存信息
通过 updateModules 执行收集需要热更新的模块,通过 websocket 向客户端发送需要热更新的模块
客户端派发更新
script 脚本 /@vite/client 会向客户端注入一段默认的代码,代码中执行的 setupWebSocket 方法会创建一个 websocket 服务用于监听服务端发送的热更新信息,接收到的信息会通过 handleMessage 方法处理
最后更新于
这有帮助吗?