Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • vite从入门到精通
  • vite插件
  • vite自动导入api插件
  • Vite 开发服务器核心原理讲解
  • Vite HMR源码级讲解
    • 🚀 1. 为什么 Vite 的 HMR 能这么快?
      • 🧠 不需要重新构建 bundle,而是定向更新实际变动文件
    • 📦 2. HMR 核心模块结构(源码层面)
      • 目录(大致)
    • 🕸 3. ModuleGraph —— HMR 的大脑
    • 📡 4. 文件变化后(HMR 整条链路)
    • ✨ 第 1 步:监听文件变化(chokidar)
    • ✨ 第 2 步:handleHMRUpdate() 处理更新
    • ✨ 第 3 步:确定哪些模块“接受更新”
      • ✔ 有 import.meta.hot.accept()
      • ❌ 没 HMR 处理器
    • ✨ 第 4 步:WebSocket 推送更新消息
    • ✨ 第 5 步:浏览器端(客户端 HMR runtime)
    • ✨ 第 6 步:如果没有模块接受更新 → 整页刷新
    • 🧩 5. 为什么 Vite 这么快?(总结)
      • 因为:
  • Vite虚拟模块插件
  • 模拟 Vite 内置env
  • vite一体化虚拟模块插件
  • vite可发布npm的虚拟插件
  • 《Vite》学习笔记
mamingjuan
2024-07-29
目录

Vite HMR源码级讲解

我会还原 Vite 源码模块结构、文件流转、WebSocket 通信、模块依赖图、热更新链路 等,让你能完全理解它是怎么做到“极速 HMR”的。


# 🚀 1. 为什么 Vite 的 HMR 能这么快?

Vite 的核心思想:

# 🧠 不需要重新构建 bundle,而是定向更新实际变动文件

Webpack:

  • 文件改了 → Webpack 重新构建部分 bundle → 发送补丁 → 替换模块

Vite:

  • 文件改了 → 浏览器重新请求这个文件的 ESM 版本
  • 如果有依赖关系 → 只更新依赖链,而不是打包

所以 Vite HMR 快速的根本原因:天然 ESM,无需 bundle,无需 diff,无需 patch。


# 📦 2. HMR 核心模块结构(源码层面)

# 目录(大致)

vite/
 ├─ server/
 │   ├─ index.ts              → 创建 dev server(核心)
 │   ├─ ws.ts                 → WebSocket 通信
 │   ├─ hmr.ts                → HMR 的依赖跟踪 & 更新逻辑
 │   ├─ moduleGraph.ts        → 模块依赖图(最关键)
 │   ├─ plugins/
 │       ├─...
 │   ├─ transformRequest.ts   → 单文件的编译/转换
 │   └─...
1
2
3
4
5
6
7
8
9
10

核心组件:

  1. ModuleGraph 模块图(追踪模块依赖)
  2. Watcher 文件监听器(chokidar)
  3. WS WebSocket 系统(向浏览器推送更新事件)
  4. 热更新处理器:server.handleHMRUpdate()
  5. 客户端 HMR 运行时(/vite/client)

# 🕸 3. ModuleGraph —— HMR 的大脑

Vite 为每个模块维护了一个图结构:

class ModuleNode {
  url: string
  importedModules: Set<ModuleNode>
  importers: Set<ModuleNode>
}
1
2
3
4
5

例子:

A.js  → import B.js
B.js  → import C.js
1
2

ModuleGraph 会记录:

  • B 的 importers = { A }
  • C 的 importers = { B }

➡️ 当 C 改了 → 需要通知 B ➡️ 如果 B 也没有 HMR 接收器 → 再通知 A(向上冒泡)


# 📡 4. 文件变化后(HMR 整条链路)

下面进入最关键部分:HMR 是怎么真的动起来的?


# ✨ 第 1 步:监听文件变化(chokidar)

在 server/index.ts 中:

watcher.on('change', (file) => {
  server.handleHMRUpdate(file)
})
1
2
3

当你编辑某个文件,比如 src/App.vue:

File changed: src/App.vue
1

# ✨ 第 2 步:handleHMRUpdate() 处理更新

重点函数:server.handleHMRUpdate(file)

它会:

  1. 找到对应的 ModuleNode
  2. 执行该模块的插件 hmr 钩子(例如 Vue 插件)
  3. 采集依赖链中接受更新的模块
  4. 通过 WebSocket 发送更新事件

# ✨ 第 3 步:确定哪些模块“接受更新”

HMR 是有两种处理方式:

# ✔ 有 import.meta.hot.accept()

→ 局部热更新(只刷新相关模块)

# ❌ 没 HMR 处理器

→ 根据依赖反向查找 “importers” → 找到最近的 HMR 边界 → 如果找不到 → 全量刷新页面

例如:

A.vue → import B.js → import C.js
1

如果改了 C.js:

  1. C.js 有没有 accept? ❌ 没有

  2. 它的 importer B.js 有没有 accept? ❌ 没有

  3. 再向上看 A.vue ✔ 有 Vue 的 HMR accept

➡️ 最终更新 A.vue(触发 Vue 局部更新)


# ✨ 第 4 步:WebSocket 推送更新消息

代码来自 ws.ts:

ws.send({
  type: 'update',
  updates: [{ path, timestamp }]
})
1
2
3
4

Vite 使用 ws://localhost:5173 建立一个 WebSocket,它只发 JSON:

{
  "type": "update",
  "updates": [
    {
      "path": "/src/components/MyComp.vue",
      "timestamp": 1679999999999
    }
  ]
}
1
2
3
4
5
6
7
8
9

# ✨ 第 5 步:浏览器端(客户端 HMR runtime)

HMR 客户端运行时在:

vite/client
1

主要逻辑:

  1. 接收 WebSocket push
  2. 根据 path 动态调用 import(path + "?t=timestamp")
  3. 替换模块
  4. 执行 import.meta.hot.accept() 回调

示例(简化):

socket.onmessage = async (msg) => {
  if (msg.type === 'update') {
    for (const update of msg.updates) {
      import(update.path + '?t=' + update.timestamp)
    }
  }
}
1
2
3
4
5
6
7

非常直接:重新 import 文件而已!

没有 patch,没有 diff,没有 bundle —— 所以快。


# ✨ 第 6 步:如果没有模块接受更新 → 整页刷新

location.reload()
1

这就是为什么有时候 HMR 失败,会自动刷新页面。


# 🧩 5. 为什么 Vite 这么快?(总结)

# 因为:

  • 使用浏览器原生 ESM (No Bundle)
  • 改哪个文件就更新哪个
  • HMR 是基于依赖图的精准更新
  • 客户端仅需要重新 import 新模块
  • 无需 patch, 无需 rebuild, 无需 Pre-bundle

这就是 Vite 能秒级热更新的根本原因。


上次更新: 2025/12/09, 03:15:37
Vite 开发服务器核心原理讲解
Vite虚拟模块插件

← Vite 开发服务器核心原理讲解 Vite虚拟模块插件→

Copyright © 2015-2025 Glitz Ma
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式