Vite 熱更新 -HMR- 原理了解一下
❝
幸福的三大要素是:有要做的事(something to do)、有要愛的人(someone to love)、有寄予希望的東西(something to hope for)
❞
大家好,我是**「柒八九」**。一個**「專注於前端開發技術 /Rust
及AI
應用知識分享」**的Coder
前言
用過Vite
進行項目開發的同學,肯定聽說過,Vite
在開發環境和生產環境是兩種不同的資源處理方式。
在開發環境,Vite
以原生 ESM 方式提供源碼,讓瀏覽器接管了打包程序的部分工作:Vite
只需要在瀏覽器請求源碼時進行轉換並按需提供源碼。根據情景動態導入代碼,即只在當前屏幕上實際使用時纔會被處理。
而在本地開發中,肯定會有本地代碼的變更處理,如何最大限度的在不刷新整體頁面的情況下,進行代碼的替換呢。這就用到 HMR[1] 這一特性。而承載HMR
的部分就是,我們需要在開發階段啓動一個Dev Server
。體現在代碼中就是我們在Vite
的配置文件 - vite.config.ts
中會有一個單獨的字段 - server
, 更詳細的解釋可以參看 vite_開發服務器選項 [2]
❝
HMR
允許我們在不刷新頁面的情況下更新代碼,比如編輯組件標記或調整樣式,這些更改會立即反映在瀏覽器中,從而實現更快的代碼交互和更好的開發體驗。❞
在生產環境中,Vite
利用Rollup
對代碼進行打包處理,並配合着tree-shaking
/ 懶加載和chunk
分割的方式爲瀏覽器提供最後的代碼資源。體現在代碼中就是我們在Vite
的配置文件 - vite.config.ts
中會有一個單獨的字段 - build
, 更詳細的解釋可以參看 vite_構建選項 [3]
我們在之前的淺聊 Vite 中介紹過了,Vite
內部打包流程。
❝
而今天我們來講講,在開發環境中,
Vite
是如何實現HMR
的。❞
當然,針對不同的打包工具,可能有自己的實現原理。如果大家對其他工具的HMR
感興趣。可以從下方鏈接中自行探索。
-
webpack-hrm[4]
-
rollup-plugin-hot[5]
當然,我們下面的內容,儘量從代碼的頂層設計去探索,如果大家想看Vite -HMR
的具體實現可以找到對應的部分,自行探索。
-
/@vite/client 源碼 [6] 下文會有對應介紹
-
vite_hmr 的核心部分 [7]
-
vite_hmr 傳播 [8] 下文會有對應介紹
好了,天不早了,乾點正事哇。
我們能所學到的知識點
❝
模塊替換
HMR 何時發生
HMR 客戶端
❞
- 模塊替換 =======
❝
模塊替換
的基本原理是,在應用程序**「運行時動態替換模塊」**。❞
大多數打包工具使用 ECMAScript
模塊(ESM
)作爲模塊,因爲它**「更容易分析導入和導出」**,這有助於確定一個模塊的替換會如何影響其他相關模塊。關於ESM
的介紹,可以看我們之前的文章~ 你真的瞭解 ESM 嗎?
一個模塊通常可以訪問 HMR API
,以處理舊模塊刪除
和新模塊新增
的情況。在 Vite
中,我們可以使用以下 API:
-
import.meta.hot.accept()[9]
-
import.meta.hot.dispose()[10]
-
import.meta.hot.prune()[11]
-
import.meta.hot.invalidate()[12]
從更高層次上看,我們就可以得到如下的模塊處理流程。
還需要注意的是,我們需要使用這些 API
才能讓 HMR
工作。例如,Vite
默認情況下會爲 CSS
文件使用這些 API
,但對於像 Vue
這樣的其他文件,我們可以使用一個 Vite
插件來使用這些 HMR API
。或者根據需要手動處理。否則,對文件的更新將導致默認情況下進行完整頁面重新加載。
針對不同的語言環境,也是需要對應的插件進行這些 api 的調用處理。下面列舉幾個比較場景的插件實現
-
React
: Fast Refresh[13] 和 @vitejs/plugin-react[14] -
Vue
: @vue/runtime-core[15] 和 @vitejs/plugin-vue[16] -
Svelte
:svelte-hmr[17] 和 @vitejs/plugin-svelte[18]
❝
在
Vite
官網中,有這樣的介紹,而 handleHotUpdate
用於處理HRM更新
。我們從 vite-vue
中就可以看到對應的處理過程。❞
上面是寫插件需要注意的地方,而我們繼續深入vite
中HRM
的對應 API 的工作原理。
accept()
import.meta.hot.accept()
當我們使用 import.meta.hot.accept()
添加一個回調時,該回調將負責**「用新模塊替換舊模塊」**。使用此 API 的模塊也稱爲 已接受模塊
。
❝
已接受模塊
創建了一個HMR 邊界
。一個HMR 邊界
包含模塊本身以及所有遞歸導入的模塊。接受模塊
通常也是HMR 邊界
的根
,因爲邊界通常是**「圖形結構」**。❞
已接受模塊
也可以根據 HMR回調
的位置縮小範圍,如果accept
中只接受一個回調,此時模塊被稱爲 自接受模塊
。
import.meta.hot.accept
有兩種函數簽名:
-
import.meta.hot.accept(cb: Function)
- 接受來自自身的更改 -
import.meta.hot.accept(deps: string | string[], cb: Function)
- 接受來自導入的模塊的更改
如果使用第一種簽名,就是自接受模塊
。
自接受模塊
export let data = [1, 2, 3]
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 用新值替換舊值
data = newModule.data
})
}
已接受模塊
import { value } from './stuff.js'
document.querySelector('#value').textContent = value
if (import.meta.hot) {
import.meta.hot.accept(['./stuff.js'], ([newModule]) => {
// 用新值重新渲染
document.querySelector('#value').textContent = newModule.value
})
}
dispose()
import.meta.hot.dispose()
當一個已接受模塊
被替換爲新模塊
,或者被移除時
,我們可以使用 import.meta.hot.dispose()
進行清理。這允許我們清理掉舊模塊創建的任何副作用
,例如刪除事件監聽器
、清除計時器
或重置狀態
。
globalThis.__my_lib_data__ = {}
if (import.meta.hot) {
import.meta.hot.dispose(() => {
// 重置全局狀態
globalThis.__my_lib_data__ = {}
})
}
prune()
import.meta.hot.prune()
當一個模塊要從運行時**「完全移除時」**,例如一個文件被刪除,我們可以使用 import.meta.hot.prune()
進行**「最終清理」**。這類似於 import.meta.hot.dispose()
,但只在模塊被移除時調用一次。
Vite
通過導入分析階段
來進行模塊清理,因爲我們能夠知道**「一個模塊不再被使用的唯一時機是當它不再被任何模塊導入」**。
以下是 Vite
使用該 API 處理 CSS HMR
的示例:
// 導入用於更新/移除 HTML 中樣式標籤的工具
import { updateStyle, removeStyle } from '/@vite/client'
updateStyle('/src/style.css', 'body { color: red; }')
if (import.meta.hot) {
// 空的回調錶示我們接受了這個模塊,但是我們可以啥都不做
// `updateStyle` 將自動刪除舊的樣式標籤。
import.meta.hot.accept()
// 當模塊不再被使用時,移除樣式
import.meta.hot.prune(() => {
removeStyle('/src/style.css')
})
}
invalidate()
import.meta.hot.invalidate()
與上述 API 不同,import.meta.hot.invalidate()
是一個**「操作」**,而不是一個生命週期鉤子。我們通常會在 import.meta.hot.accept
中使用它,在運行時可能會意識到模塊無法安全更新時,我們需要退出。
當調用這個方法時,Vite服務器
將被告知**「該模塊已失效」**,就像該模塊已被更新一樣。HMR傳播
將再次執行,以確定其導入者是否可以遞歸地接受此更改。
export let data = [1, 2, 3]
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 如果 `data` 導出被刪除或重命名
if (!(data in newModule)) {
// 退出並使模塊失效
import.meta.hot.invalidate()
}
})
}
上述就是針對涉及到HRM
的相關 API 的簡單介紹。更具體的解釋,可以參考 vite_hmr[19]
- HMR 何時發生 ===========
既然,HMR API
賦予了我們替換
和刪除
模塊的能力,光有能力是不行的,我們需要了解它們何時纔會起作用。其實,HMR
通常發生在**「編輯文件之後」**,但是之後又發生了啥,我們不得而知,這就是我們這節需要了解的內容。
它的總體流程如下:
讓我們來逐步揭開它神祕的面紗!
編輯文件
當我們編輯文件並保存時,HMR
就開始了。文件系統監視器(例如 chokidar[20])會檢測到更改並將編輯後的**「文件路徑」**傳遞到下一步。
處理編輯後的模塊
Vite 開發服務器
得知了編輯後的文件路徑。然後**「使用文件路徑來找到模塊圖中相關的模塊」**。
❝
文件
和模塊
是兩個不同的概念,一個文件可能對應一個或多個模塊。
例如,一個Vue
文件可以編譯成一個JavaScript模塊
和一個相關的CSS模塊
。❞
然後,這些模塊被傳遞給 Vite 插件
的 handleHotUpdate()
鉤子進行進一步處理。它們可以選擇過濾
或擴展
模塊數組。最終的模塊將傳遞到下一步。
過濾模塊數組
在上一節介紹HMR API
時,就介紹過handleHotUpdate
, 爲了節省時間,我們再次將其搬過來。
function vuePlugin() {
return {
name: 'vue',
handleHotUpdate(ctx) {
if (ctx.file.endsWith('.vue')) {
const oldContent = cache.get(ctx.file)
const newContent = await ctx.read()
// 如果編輯文件時只有樣式發生了變化,我們可以過濾掉 JS 模塊,並僅觸發 CSS 模塊的 HMR。
if (isOnlyStyleChanged(oldContent, newContent)) {
return ctx.modules.filter(m => m.url.endsWith('.css'))
}
}
}
}
}
上面只是一個簡單示例,像我們比較熟悉的vite-vue
其實處理HMR
的邏輯差不多,只不過新增了一些額外的校驗和處理。
更詳細的代碼是可以實現,可以參考 github_vite-plugin-vue[21]
擴展模塊數組
function globalCssPlugin() {
return {
name: 'global-css',
handleHotUpdate(ctx) {
if (ctx.file.endsWith('.css')) {
// 如果編輯了 CSS 文件,我們還會觸發此特殊的 `virtual:global-css` 模塊的 HMR,該模塊需要重新轉換。
const mod = ctx.server.moduleGraph.getModuleById('virtual:global-css')
if (mod) {
return ctx.modules.concat(mod)
}
}
}
}
}
模塊失效
在 HMR傳播
之前,我們需要將最終更新的模塊數組及其導入者遞歸失效
。每個模塊的**「轉換代碼都將被移除,並附加一個失效時間戳」**。時間戳將用於在客戶端的下一個請求中獲取新模塊。
HMR 傳播
現在,最終的更新模塊數組將通過 HMR 傳播
。這是HMR
是否起作用的核心步驟,如果傳播過程有數據丟失,那麼HMR
就會達不到我們想要的預期,也就是部分模塊沒及時更新或者更新失敗了。
❝
HMR 傳播
就是以更新的模塊
作爲起點,向四周擴散,最後找到與該模塊相關的模塊信息,並且形成一個**「無形」**的環。或者給它起一個更高大上的名字 -HMR 邊界
如果所有更新的模塊都在一個邊界內,
Vite 開發服務器
將通知HMR 客戶端
通知接受的模塊執行HMR
。如果有些模塊不在邊界內,則會觸發完整的頁面重新加載。
❞
案例分析
爲了更好地理解它是如何工作的,讓我們來看幾個例子。
-
app.jsx
是一個接受模塊
,也就意味着,在其內部觸發了import.meta.hot.accept()
-
與
app.jsx
相關的文件有stuff.js
和utils.js
,也就意味着,它們三個會形成一個HMR 邊界
情況 1
如果更新 stuff.js
,傳播將**「遞歸查找」**其導入者以找到一個接受的模塊
。在這種情況下,我們將發現 app.jsx
是一個接受的模塊。但在結束傳播之前,我們需要確定 app.jsx
是否可以接受來自 stuff.js
的更改。這取決於 import.meta.hot.accept()
的調用方式。
-
情況 1(a): 如果
app.jsx
是自接受
的,或者它接受來自stuff.js
的更改,我們可以在這裏停止傳播,因爲沒有其他來自stuff.js
的導入者。然後,HMR 客戶端
將通知app.jsx
執行HMR
。 -
情況 1(b): 如果
app.jsx
不接受這個更改,我們將繼續向上傳播以找到一個接受的模塊。但由於沒有其他接受的模塊,我們將到達項目的**「根節點」** -index.html
文件。此時將觸發整個項目的重新加載。
情況 2:
如果更新 main.js
或 other.js
,傳播將再次遞歸查找其**「導入者」**。然而,沒有接受的模塊,我們將到達項目的**「根節點」** - index.html
文件。因此,將觸發完整的頁面重新加載。
情況 3:
如果更新 app.jsx
,我們立即發現它是一個接受的模塊。然而,一些模塊可能無法更新其自身的更改。我們可以通過檢查它們是否是自接受的模塊來確定它們是否可以更新自身。
-
情況 3(a):如果
app.jsx
是自接受的,我們可以在這裏停止,並讓HMR 客戶端
通知它執行 HMR。 -
情況 3(b):如果
app.jsx
不是自接受的,我們將繼續向上傳播以找到一個接受的模塊。但由於它們都沒有,我們將到達項目的**「根節點」** -index.html
文件,將觸發完整的頁面重新加載。
情況 4:
如果更新 utils.js
,傳播將再次遞歸查找其導入者。首先,我們將找到 app.jsx
作爲接受的模塊,並在那裏停止傳播(例如情況 1(a)
)。然後,我們也會遞歸查找 other.js
及其導入者,但沒有接受的模塊,我們將到達項目的**「根節點」** - index.html
文件。
❝
最後,
HMR傳播
的結果是是否需要進行完整頁面重新加載,或者是否應該在客戶端應用 HMR 更新。❞
- HMR 客戶端 ==========
在 Vite
應用中,我們可能會注意到 HTML
中添加了一個特殊的腳本<script type="module" src="/@vite/client"></script>
,請求 /@vite/client
。這個腳本包含了 HMR 客戶端
!
我們可以在Chrome-devtool-sources
中進行查看
❝
HMR 客戶端
負責:
與
Vite 開發服務器
建立WebSocket
連接。監聽來自服務器的
HMR 載荷
。在運行時提供和觸發
HMR API
。將任何事件發送回
Vite 開發服務器
。❞
從更廣泛的角度來看,HMR 客戶端
幫助將 Vite 開發服務器
和 HMR API
粘合在一起。
客戶端初始化
在 HMR 客戶端
能夠從 Vite 開發服務器
接收任何消息之前,它首先需要建立與其的連接,通常是通過 WebSockets[22]。
下面是一個設置 WebSocket
連接並處理 HMR 傳播
結果的示例:
/@vite/client
const ws = new WebSocket('ws://localhost:5173')
ws.addEventListener('message', ({ data }) => {
const payload = JSON.parse(data)
switch (payload.type) {
case '...':
// 處理載荷...
}
})
// 將任何事件發送回 Vite 開發服務器
ws.send('...')
除此之外,HMR 客戶端
還初始化了一些處理 HMR
所需的狀態,並導出了幾個 API,例如 createHotContext()
,供使用 HMR API
的模塊使用。例如:
app.jsx
// 由 Vite 的導入分析插件注入
import { createHotContext } from '/@vite/client'
import.meta.hot = createHotContext('/src/app.jsx')
export default function App() {
return <div>Hello Front789</div>
}
// 由 `@vitejs/plugin-react` 注入
if (import.meta.hot) {
// ...
}
傳遞給 createHotContext()
的 URL 字符串(也稱爲 owner 路徑
)有助於**「標識哪個模塊能夠接受更改」**。在createHotContext
將註冊的 HMR 回調分配單例類型,而該類型用於存儲owner 路徑
到接受回調、處理回調和修剪回調之間的關聯信息。const ownerPathToAcceptCallbacks = new Map<string, Function[]>()
這基本上就是模塊如何與 HMR 客戶端交互並執行 HMR 更改的方式。
處理來自服務器的信息
建立 WebSocket
連接後,我們可以開始處理來自 Vite 開發服務器
的信息。
/@vite/client
ws.addEventListener('message', ({ data }) => {
const payload = JSON.parse(data)
switch (payload.type) {
case 'full-reload': {
location.reload()
break
}
case 'update': {
const updates = payload.updates
// => { type: string, path: string, acceptedPath: string, timestamp: number }[]
for (const update of updates) {
handleUpdate(update)
}
break
}
case 'prune': {
handlePrune(payload.paths)
break
}
// 處理其他載荷類型...
}
})
上面的示例處理了 HMR 傳播
的結果,根據 full-reload
和 update
信息類型觸發完整頁面重新加載或 HMR 更新
。當模塊不再使用時,它還處理修剪。
還有更多類型的信息類型需要處理
-
connected
:當建立WebSocket
連接時發送。 -
error
:當服務器端出現錯誤時發送,Vite
可以在瀏覽器中顯示錯誤覆蓋層。 -
custom
:由Vite
插件發送,通知客戶端任何事件。對於客戶端和服務器之間的通信非常有用。
接下來,讓我們看看 HMR 更新
實際上是如何工作的。
HMR 更新
❝
HMR 傳播
期間找到的每個HMR 邊界
通常對應一個HMR 更新
。❞
在 Vite
中,更新採用這種簽名:
interface Update {
// 更新的類型
type: 'js-update' | 'css-update'
// 接受模塊(HMR 邊界根)的 URL 路徑
path: string
// 被接受的 URL 路徑(通常與上面的路徑相同)
acceptedPath: string
// 更新發生的時間戳
timestamp: number
}
在 Vite
中,它被區分爲 JS 更新
或 CSS 更新
,其中 CSS 更新
被特別處理爲在更新時簡單地交換 HTML
中的鏈接標籤。
對於 JS 更新
,我們需要找到相應的模塊,以調用其 import.meta.hot.accept()
回調,以便它可以對自身應用 HMR。由於在 createHotContext()
中我們已經**「將路徑註冊爲第一個參數」**,因此我們可以通過更新的路徑輕鬆找到匹配的模塊。有了更新的時間戳,我們還可以獲取模塊的新版本以傳遞給 import.meta.hot.accept()
。
下面是一個實現的示例:
/@vite/client
// 由 `createHotContext()` 填充的映射
const ownerPathToAcceptCallbacks = new Map<string, Function[]>()
async function handleUpdate(update: Update) {
const acceptCbs = ownerPathToAcceptCallbacks.get(update.path)
const newModule = await import(`${update.acceptedPath}?t=${update.timestamp}`)
for (const cb of acceptCbs) {
cb(newModule)
}
}
之前我們就介紹過,import.meta.hot.accept()
有兩個函數簽名
-
import.meta.hot.accept(cb: Function)
-
import.meta.hot.accept(deps: string | string[], cb: Function)
上面的實現對於第一個函數簽名(自接受模塊
)的情況處理良好,但對於第二個函數簽名則不適用。第二個函數簽名的**「回調函數只有在依賴項發生更改時才需要被調用」**。爲了解決這個問題,我們可以將每個回調函數綁定到一組依賴項。
app.jsx
import { add } from './utils.js'
import { value } from './stuff.js'
if (import.meta.hot) {
import.meta.hot.accept(...)
// { deps: ['/src/app.jsx'], fn: ... }
import.meta.hot.accept('./utils.js', ...)
// { deps: ['/src/utils.js'], fn: ... }
import.meta.hot.accept(['./stuff.js'], ...)
// { deps: ['/src/stuff.js'], fn: ... }
}
然後,我們可以使用 acceptedPath
來匹配依賴關係並觸發正確的回調函數。
例如,如果更新了 stuff.js
,那麼 acceptedPath
將是 /src/stuff.js
,而 path
將是 /src/app.jsx
。這樣,我們可以通知擁有者路徑
接受路徑(acceptedPath
)已經更新,而擁有者可以處理其更改。我們可以調整 HMR 處理程序如下:
/@vite/client
// 由 `createHotContext()` 填充的映射
const ownerPathToAcceptCallbacks = new Map<
string,
{ deps: string[]; fn: Function }[]
>()
async function handleUpdate(update: Update) {
const acceptCbs = ownerPathToAcceptCallbacks.get(update.path)
const newModule = await import(`${update.acceptedPath}?t=${update.timestamp}`)
for (const cb of acceptCbs) {
// 確保只執行可以處理 `acceptedPath` 的回調函數
if (cb.deps.some((deps) => deps.includes(update.acceptedPath))) {
cb.fn(newModule)
}
}
}
但我們還沒有完成!在導入新模塊之前,我們還需要確保正確處理舊模塊,使用 import.meta.hot.dispose()
。
/@vite/client
// 由 `createHotContext()` 填充的映射
const ownerPathToAcceptCallbacks = new Map<
string,
{ deps: string[]; fn: Function }[]
>()
const ownerPathToDisposeCallback = new Map<string, Function>()
async function handleUpdate(update: Update) {
const acceptCbs = ownerPathToAcceptCallbacks.get(update.path)
// 如果有的話調用 dispose 回調
ownerPathToDisposeCallbacks.get(update.path)?.()
const newModule = await import(`${update.acceptedPath}?t=${update.timestamp}`)
for (const cb of acceptCbs) {
// 確保只執行可以處理 `acceptedPath` 的回調函數
if (cb.deps.some((deps) => deps.includes(update.acceptedPath))) {
cb.fn(newModule)
}
}
}
上面的代碼基本上實現了大部分的 HMR 客戶端
!
HMR 修剪
我們之前聊過,在 導入分析
階段,Vite
內部處理了 HMR 修剪
。當一個模塊不再被任何其他模塊導入時,Vite 開發服務器
將向 HMR 客戶端
發送一個 { type: 'prune', paths: string[] }
載荷,其中它將獨立地在運行時修剪模塊。
/@vite/client
// 由 `createHotContext()` 填充的映射
const ownerPathToDisposeCallback = new Map<string, Function>()
const ownerPathToPruneCallback = new Map<string, Function>()
function handlePrune(paths: string[]) {
for (const p of paths) {
ownerPathToDisposeCallbacks.get(p)?.()
ownerPathToPruneCallback.get(p)?.()
}
}
HMR 作廢
與其他 HMR API
不同,import.meta.hot.invalidate()
是可以在 import.meta.hot.accept()
中調用的動作,以退出 HMR
。在 /@vite/client
中,只需發送一個 WebSocket
消息到 Vite 開發服務器
:
/@vite/client
// `ownerPath` 來自於 `createHotContext()`
function handleInvalidate(ownerPath: string) {
ws.send(
JSON.stringify({
type: 'custom',
event: 'vite:invalidate',
data: { path: ownerPath }
})
)
}
當 Vite 服務器
接收到此消息時,它將從其導入者再次執行 HMR 傳播
,結果(完整重新加載或 HMR 更新)將發送回 HMR 客戶端
。
HMR 事件
雖然不是 HMR
必需的,但 HMR 客戶端
還可以在運行時發出事件,當收到特定信息時。import.meta.hot.on
和 import.meta.hot.off
可以用於監聽和取消監聽這些事件。
if (import.meta.hot) {
import.meta.hot.on('vite:invalidate', () => {
// ...
})
}
發出和跟蹤這些事件與上面處理 HMR 回調的方式非常相似。
/@vite/client(URL)
+ const eventNameToCallbacks = new Map<string, Set<Function>>()
// `ownerPath` 來自於 `createHotContext()`
function handleInvalidate(ownerPath: string) {
+ eventNameToCallbacks.get('vite:invalidate')?.forEach((cb) => cb())
ws.send(
JSON.stringify({
type: 'custom',
event: 'vite:invalidate',
data: { path: ownerPath }
})
)
}
HMR 數據
最後,HMR 客戶端
還提供了一種存儲數據以在 HMR API 之間共享的方法,即 import.meta.hot.data
。這些數據也可以傳遞給 import.meta.hot.dispose()
和 import.meta.hot.prune()
的 HMR 回調函數。
保留數據也與我們跟蹤 HMR 回調的方式類似。
以 HMR 修剪
代碼爲例:
/@vite/client
// 由 `createHotContext()` 填充的映射
const ownerPathToDisposeCallback = new Map<string, Function>()
const ownerPathToPruneCallback = new Map<string, Function>()
+ const ownerPathToData = new Map<string, Record<string, any>>()
function handlePrune(paths: string[]) {
for (const p of paths) {
+ const data = ownerPathToData.get(p)
+ ownerPathToDisposeCallbacks.get(p)?.(data)
+ ownerPathToPruneCallback.get(p)?.(data)
}
}
Reference
[1]
HMR: https://www.youtube.com/watch?v=e5M_5jKPaL4
[2]
vite_開發服務器選項: https://cn.vitejs.dev/config/server-options.html
[3]
vite_構建選項: https://cn.vitejs.dev/config/build-options.html
[4]
webpack-hrm: https://blog.nativescript.org/deep-dive-into-hot-module-replacement-with-webpack-part-one-the-basics/
[5]
rollup-plugin-hot: https://github.com/rixo/rollup-plugin-hot
[6]
/@vite/client 源碼: https://github.com/vitejs/vite/blob/main/packages/vite/src/client/client.ts
[7]
vite_hmr 的核心部分: https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts
[8]
vite_hmr 傳播: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/hmr.ts
[9]
import.meta.hot.accept(): https://vitejs.dev/guide/api-hmr.html#hot-accept-cb
[10]
import.meta.hot.dispose(): https://vitejs.dev/guide/api-hmr.html#hot-dispose-cb
[11]
import.meta.hot.prune(): https://vitejs.dev/guide/api-hmr.html#hot-prune-cb
[12]
import.meta.hot.invalidate(): https://vitejs.dev/guide/api-hmr.html#hot-invalidate-message-string
[13]
Fast Refresh: https://github.com/facebook/react/tree/main/packages/react-refresh
[14]
@vitejs/plugin-react: https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/src/fast-refresh.ts
[15]
@vue/runtime-core: https://github.com/vuejs/core/blob/main/packages/runtime-core/src/hmr.ts
[16]
@vitejs/plugin-vue: https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/src/main.ts
[17]
svelte-hmr: https://github.com/sveltejs/svelte-hmr/tree/master/packages/svelte-hmr
[18]
@vitejs/plugin-svelte: https://github.com/sveltejs/vite-plugin-svelte/blob/main/packages/vite-plugin-svelte/src/utils/compile.js
[19]
vite_hmr: https://vitejs.dev/guide/api-hmr.html
[20]
chokidar: https://github.com/paulmillr/chokidar
[21]
github_vite-plugin-vue: https://github.com/vitejs/vite-plugin-vue/blob/46d0baa45c9e7cf4cd3ed773af5ba9f2a503b446/packages/plugin-vue/src/index.ts#L156
[22]
WebSockets: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/7r0f6IpgXUau4RNnu1yoQw