大家好,我是 Maneshwar。我正在開發 git-lrc,一個微型 AI 程式碼審查器,它會在每次提交時執行。它是免費的,原始碼已發佈在 GitHub 上。請為 git-lrc 點贊,幫助開發者發現這個專案。歡迎試用並分享您的回饋,以幫助我們改進專案。
你一生中大約執行過 1000 次npm run dev 。
你看到終端閃爍了一下。
您已切換到瀏覽器。
奇蹟發生了。
但你真的了解事情的真相嗎?我是說,真的了解嗎?
今天我們將深入理解 shell、進程生成、模組圖、熱模組替換、React 快速刷新。
npm run dev
你的 shell 會在PATH中尋找npm ,通常它位於nvm安裝 Node 的目錄中,或是位於類似/usr/local/bin/npm系統路徑下。然後它會將控制權交給 npm CLI,而 npm CLI 本身只是一個 Node.js 腳本。
然後 npm 會做一件事:讀取你的package.json 。
{
"scripts": {
"dev": "vite"
}
}
它找到了"dev": "vite"並準備執行它。
npm 會暫時將./node_modules/.bin加入你的PATH中,然後產生一個子程序來執行dev指令。
因此, "vite"解析為./node_modules/.bin/vite ,它是指向node_modules/vite/bin/vite.js中實際Vite二進位檔案的符號連結。
沒什麼神奇的,只是Node執行了一個JS檔而已。
Vite會讀取你的vite.config.js (或.ts )文件,註冊插件,並在提供任何內容之前執行一些非常巧妙的操作。
Vite 使用 Go 編寫的esbuild ,速度快得驚人,可以預處理你的node_modules依賴項。
它有兩個作用:
將 CommonJS 轉換為 ESM。瀏覽器無法原生載入require() ,esbuild 會重寫它。
esbuild 將包含大量內部文件的套件合併成一個。例如, lodash有 600 多個小檔案。 esbuild 會將它們合併,因此瀏覽器只需發出一個請求,而不是 600 個。
結果快取在.vite/deps/中。執行 touch node_modules指令?快取將被清除。
否則,重新啟動時將完全跳過此步驟。

Vite 在localhost:5173上啟動一個普通的 Node.js HTTP 伺服器。
沒有webpack開發伺服器的魔法,也沒有複雜的中間件鏈。
當瀏覽器請求文件時,Vite:
從磁碟讀取
即時轉換(JSX → JS,TypeScript → JS, .css → 注入的<style> )
將其用作原生ES 模組
瀏覽器本身使用<script type="module">來處理模組解析。
關鍵在於: Vite 在開發模式下根本不會打包你的原始碼。
瀏覽器將每個檔案作為 ES 模組單獨取得。
這就是為什麼無論專案大小,Vite 的開發啟動速度幾乎是瞬間完成的,因為它只處理瀏覽器實際請求的內容。

每個箭頭都代表一個真實的HTTP請求。瀏覽器會自動為你完成圖遍歷。
當請求檔案時,Vite 會建立一個內部模組依賴關係圖,也就是誰匯入了什麼的映射。

這個圖是惰性的,只有被要求的節點才會存在其中。
如果你從未導航到某個路由,那麼這些元件就永遠不會被獲取,也不會出現在圖中。
這就是HMR速度快的原因。
當檔案發生變更時,Vite 不會掃描所有程式碼。
它只是沿著這條曲線走。
Vite 使用chokidar ,它封裝了作業系統原生的檔案系統事件:
作業系統 | 應用程式介面 | 機制 |
|---|---|---|
| Linux | inotify | 核心直接監視 inode |
| macOS | FSEvents | 核心服務檔案系統事件 API |
| Windows | ReadDirectoryChangesW | Win32 API |
零輪詢。作業系統會在編輯器刷新到磁碟的瞬間立即通知 Vite。
Vite 從更改的檔案開始向上遍歷模組圖,查找第一個透過import.meta.hot.accept()註冊了 HMR 處理程序的祖先。

使用 React,你永遠不需要自己寫import.meta.hot.accept() 。
@vitejs/plugin-react插件(使用React Fast Refresh )會在元件轉換時自動將此程式碼注入到每個元件檔案中。您對此不可見。
如果找不到邊界,則一直到main.jsx都會重新載入整個頁面。
當您編輯vite.config.js或不支援 HMR 的非元件 JS 實用程式時,就會發生這種情況。
Vite 會透過其插件管道 JSX 轉換、任何 Babel 插件等重新執行Button.jsx
結果是在記憶體中建立了一個新的 ES 模組。
HMR客戶端(Vite注入到頁面中的一個小型腳本)被告知有更新可用,並呼叫:
import('/components/Button.jsx?t=1718023456789')
?t=timestamp查詢參數用於清除快取。瀏覽器看到一個之前從未取得過的 URL,會發出真正的 HTTP 請求,從而取得新的模組。
hot.dispose和hot.acceptVite 的 HMR 執行時圍繞兩個函數建置,這兩個函數需要您(或者,如果是 React,則需要插件)在import.meta.hot中註冊:
// In a module that wants to handle its own updates:
// Clean up before this module is replaced —
// cancel timers, unsubscribe, remove listeners.
import.meta.hot.dispose((data) => {
clearInterval(timer)
})
// Receive the freshly imported module and re-wire things.
import.meta.hot.accept((newModule) => {
// newModule is the updated exports
})
一個微妙但重要的細節:Vite 的 HMR 並不會真的取代鏈上所有導入的原始模組物件。
接收模組(即 HMR 邊界)負責接收新的匯出資料並套用它們。
這種簡化的模型使得 Vite 可以省去重寫每個導入器的昂貴工作,並且足以滿足幾乎所有實際開發場景的需求。
dispose用於取消setInterval 、取消訂閱 store 或移除事件監聽器,任何如果舊模組被棄用就會造成記憶體洩漏的操作。
關於 CSS 的說明:樣式表的處理方式略有不同。當
.css檔案發生變更時,Vite 不會動態地重新匯入 JS 模組,而是將<link>(或<style>)標籤替換為更新後的樣式表,這樣可以避免未樣式化內容的閃爍。
React Fast Refresh 是 React 團隊建構的一個獨立系統。當新的元件模組發佈時,它會:
比較鉤子函數的簽名。如果Button在useState, useEffect之前和之後都具有useState, useEffect則狀態會被保留。加入或移除鉤子函數?狀態只會重置該元件。
尋找 React fiber 樹中所有存活的實例。
精確地重新渲染僅針對受影響的子樹。父元件不會重新渲染。兄弟元件也不會重新渲染。只有更改後的元件及其子元件會重新渲染。

這就是為什麼在表單填寫過程中,您可以編輯元件的樣式,而表單不會重設的原因。表單狀態保存在父元件或同級元件中——快速刷新從未觸及過它。

如果你使用的是 webpack 的開發伺服器,即使啟用了熱模組替換 (HMR),檔案儲存也會觸發打包重建。對於中等規模的專案來說,這會導致儲存和瀏覽器更新之間出現明顯的延遲。
使用 Vite 的原生 ESM 方法,伺服器幾乎什麼都不做(只需轉換一個檔案),瀏覽器只需取得一個 URL,快速刷新功能只需修改一個子樹。
整個往返過程通常非常快,隨著專案的增長,速度也會保持很快。
這並非微小的改進。
它從根本上改變了你的工作方式——編輯→查看結果幾乎是瞬間完成的。

人工智慧代理編寫程式碼速度很快。但它們也會悄無聲息地刪除邏輯、改變行為,甚至引入漏洞——而且不會事先通知你。你往往會在生產環境中才發現這些問題。
git-lrc 解決了這個問題。它會接入 git commit 流程,並在每次提交之前檢查差異。只需 60 秒即可完成設定。完全免費。 *
歡迎任何反饋或貢獻!它已上線,原始碼公開,任何人都可以使用。
⭐ 在 GitHub 上給它點個星:
{% github=https://github.com/HexmosTech/git-lrc
原文出處:https://dev.to/lovestaco/what-happens-when-you-run-npm-run-dev-ae9