Skip to content
Created: July 29, 2024

How does it achieve "extremely fast HMR".

🚀 1. Why is Vite’s HMR so fast? ​

The core idea of Vite:

🧠 There is no need to rebuild the bundle, but the actual changed files are updated in a targeted manner ​

Webpack:

  • File changed → Webpack rebuilds part of the bundle → Send patch → Replace module

Vite:

  • The file has been changed → The browser re-requests the ESM version of this file
  • If there are dependencies → only update the dependency chain, not package

So the fundamental reason why Vite HMR is fast: Natural ESM, no bundle, no diff, no patch.

📦 2. HMR core module structure ​

Table of contents (approximately) ​

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

Core components:

  1. ModuleGraph module graph (tracking module dependencies)
  2. Watcher file listener (chokidar)
  3. WS WebSocket System (push update events to the browser)
  4. Hot update handler: server.handleHMRUpdate()
  5. Client HMR Runtime (/vite/client)

🕸 3. ModuleGraph - the brain of HMR ​

Vite maintains a graph structure for each module:

ts
class ModuleNode {
  url: string
  importedModules: Set<ModuleNode>
  importers: Set<ModuleNode>
}

Example:

A.js  → import B.js
B.js  → import C.js

ModuleGraph will log:

  • B's importers =
  • C's importers =

➡️ When C changes → B needs to be notified ➡️ If B does not have an HMR receiver → Notify A again (bubbling up)

📡 4. After file changes (HMR entire link) ​

Now comes the most critical part: How does HMR really move?

✨ Step 1: Monitor file changes (chokidar) ​

In server/index.ts:

ts
watcher.on('change', (file) => {
  server.handleHMRUpdate(file)
})

When you edit a file, such as src/App.vue:

File changed: src/App.vue

✨ Step 2: handleHMRUpdate() handles updates ​

Key function: server.handleHMRUpdate(file)

It will:

  1. Find the corresponding ModuleNode
  2. Execute the module's plugin hmr hook (e.g. Vue plugin)
  3. Collect modules in the dependency chain that accept updates
  4. Send update events via WebSocket

✨ Step 3: Determine which modules "accept updates" ​

HMR has two processing methods:

✔ There is import.meta.hot.accept() ​

→ Partial hot update (only refresh relevant modules)

❌ No HMR processor ​

→ Reverse search for “importers” based on dependencies → Find the nearest HMR boundary → If not found → Fully refresh the page

For example:

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

If C.js is changed:

  1. Does C.js accept? ❌ No

  2. Does its importer B.js accept it? ❌ No

  3. Look up again A.vue ✔ HMR accept with Vue

➡️Finally update A.vue (trigger Vue partial update)

✨ Step 4: WebSocket Push Update Message ​

Code from ws.ts:

ts
ws.send({
  type: 'update',
  updates: [{ path, timestamp }]
})

Vite uses ws://localhost:5173 to create a WebSocket, which only sends JSON:

json
{
  "type": "update",
  "updates": [
    {
      "path": "/src/components/MyComp.vue",
      "timestamp": 1679999999999
    }
  ]
}

✨ Step 5: Browser side (client HMR runtime) ​

The HMR client runs at:

vite/client

Main logic:

  1. Receive WebSocket push
  2. Dynamically call import(path + "?t=timestamp") based on path
  3. Replace module
  4. Execute import.meta.hot.accept() callback

Example (simplified):

js
socket.onmessage = async (msg) => {
  if (msg.type === 'update') {
    for (const update of msg.updates) {
      import(update.path + '?t=' + update.timestamp)
    }
  }
}

Very straightforward: Just re-import the file!

No patches, no diffs, no bundles - so fast.

✨ Step 6: If no module accepts the update → Full page refresh ​

js
location.reload()

This is why sometimes HMR fails and automatically refreshes the page.

🧩 5. Why is Vite so fast?(Summary) ​

Because: ​

  • Use browser native ESM (No Bundle)
  • Whichever file is changed will be updated.
  • HMR is an accurate update based on dependency graph
  • The client only needs to re-import the new module
  • No patch, no rebuild, no pre-bundle required

This is the fundamental reason why Vite can be hot updated in seconds.