Vite 模式
Vite 第 8 版以上專案的建置工具與開發伺服器模式。涵蓋設定、環境變數、代理設定、程式庫模式、相依性預先打包,以及常見的生產環境陷阱。
使用時機
- 設定
vite.config.ts或vite.config.js - 設定環境變數或
.env檔案 - 設定開發伺服器代理以連接 API 後端
- 最佳化建置輸出(chunk、最小化、資源)
- 使用
build.lib發行程式庫 - 疑難排解相依性預先打包或 CJS/ESM 互操作
- 除錯 HMR、開發伺服器或建置錯誤
- 選擇或排序 Vite 外掛
運作原理
- 開發模式將原始檔案視為原生 ESM 提供服務——無需綑綁。轉換針對每個模組請求按需進行,因此冷啟動快速,HMR 精確。
- 建置模式使用 Rolldown(第 7 版以上)或 Rollup(第 5–6 版)將應用程式綑綁以用於生產環境,並進行搖樹最佳化、程式碼分割與基於 Oxc 的最小化。
- 相依性預先打包透過 esbuild 將 CJS/UMD 相依性轉換為 ESM 一次,並將結果快取於
node_modules/.vite,後續啟動可跳過此步驟。 - 外掛在開發與建置過程中共用統一的介面——同一個外掛物件適用於開發伺服器的按需轉換以及生產管線。
- 環境變數在建置時靜態內嵌。
VITE_前綴的變數成為綑綁結果中的公開常數;未前綴的任何內容對客戶端程式碼不可見。
範例
設定結構
基本設定
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname },
},
})
條件式設定
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd()) // 僅 VITE_ 前綴(安全)
return {
plugins: [react()],
server: command === 'serve' ? { port: 3000 } : undefined,
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
關鍵設定選項
| 鍵 | 預設值 | 說明 |
|---|---|---|
root | '.' | 專案根目錄(index.html 所在位置) |
base | '/' | 已部署資源的公開基礎路徑 |
envPrefix | 'VITE_' | 客戶端暴露的環境變數前綴 |
build.outDir | 'dist' | 輸出目錄 |
build.minify | 'oxc' | 最小化工具('oxc'、'terser' 或 false) |
build.sourcemap | false | true、'inline' 或 'hidden' |
外掛
必備外掛
大多數外掛需求可由少數幾個維護良好的套件滿足。在自行編寫前,請優先使用這些。
| 外掛 | 用途 | 使用時機 |
|---|---|---|
@vitejs/plugin-react-swc | 透過 SWC 的 React HMR + Fast Refresh | React 應用程式的預設(比 Babel 變體更快) |
@vitejs/plugin-react | 透過 Babel 的 React HMR + Fast Refresh | 僅當你需要 Babel 外掛(emotion、MobX 裝飾器)時 |
@vitejs/plugin-vue | Vue 3 SFC 支援 | Vue 應用程式 |
vite-plugin-checker | 在工作執行緒中執行 tsc + ESLint,搭配 HMR 覆蓋層 | 任何 TypeScript 應用程式——Vite 在 vite build 期間不會進行型別檢查 |
vite-tsconfig-paths | 遵循 tsconfig.json 的 paths 別名 | 當你已在 tsconfig.json 中設定別名時 |
vite-plugin-dts | 在程式庫模式下發出 .d.ts 檔案 | 發行 TypeScript 程式庫 |
vite-plugin-svgr | 將 SVG 導入為 React 元件 | 使用 SVG 作為元件的 React 應用程式 |
rollup-plugin-visualizer | 綑綁結果的樹狀圖/旭日圖報告 | 定期綑綁大小審計(使用 enforce: 'post') |
vite-plugin-pwa | 零設定的 PWA + Workbox | 離線可用的應用程式 |
重要提醒: vite build 會轉譯但不會進行型別檢查。除非你新增 vite-plugin-checker 或在 CI 中執行 tsc --noEmit,否則型別錯誤會靜默地發佈到生產環境。
自行編寫自訂外掛
自行編寫的情況很少見——大多數需求可由現有外掛滿足。當你真的需要時,請先在 vite.config.ts 中內嵌編寫,僅在重複使用時才提取出來。
// vite.config.ts — 最小化內嵌外掛
function myPlugin(): Plugin {
return {
name: 'my-plugin', // 必要,必須唯一
enforce: 'pre', // 'pre' | 'post'(選用)
apply: 'build', // 'build' | 'serve'(選用)
transform(code, id) {
if (!id.endsWith('.custom')) return
return { code: transformCustom(code), map: null }
},
}
}
關鍵鉤子: transform(修改原始碼)、resolveId + load(虛擬模組)、transformIndexHtml(注入到 HTML)、configureServer(新增開發中介軟體)、hotUpdate(自訂 HMR——在 v7+ 中取代已棄用的 handleHotUpdate)。
虛擬模組使用 \0 前綴慣例——resolveId 回傳 '\0virtual:my-id' 以讓其他外掛跳過。使用者程式碼導入 'virtual:my-id'。
有關完整的外掛 API,請參閱 vite.dev/guide/api-plugin。在開發期間使用 vite-plugin-inspect 來除錯轉換管線。
HMR API
框架外掛(@vitejs/plugin-react、@vitejs/plugin-vue 等)會自動處理 HMR。僅在建立自訂狀態儲存、開發工具或與框架無關且需要在更新間保持狀態的公用程式時,才直接使用 import.meta.hot。
// src/store.ts — 手動 HMR,適用於一般模組
if (import.meta.hot) {
// 在更新間保持狀態(必須**變異**,絕不能重新指派 .data)
import.meta.hot.data.count = import.meta.hot.data.count ?? 0
// 在模組被取代前清理副作用
import.meta.hot.dispose((data) => clearInterval(data.intervalId))
// 接受此模組自身的更新
import.meta.hot.accept()
}
所有 import.meta.hot 程式碼都會在生產建置中被搖樹移除——無需額外移除保護。
環境變數
Vite 會按順序載入 .env、.env.local、.env.[mode]、.env.[mode].local(後者覆蓋前者);*.local 檔案會被 .gitignore 忽略,用於存放本機機密。
客戶端存取
只有 VITE_ 前綴的變數會暴露給客戶端程式碼:
import.meta.env.VITE_API_URL // 字串
import.meta.env.MODE // 'development' | 'production' | 自訂
import.meta.env.BASE_URL // base 設定值
import.meta.env.DEV // 布林值
import.meta.env.PROD // 布林值
import.meta.env.SSR // 布林值
在設定中使用環境變數
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) // 僅 VITE_ 前綴(安全)
return {
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
安全性
VITE_ 前綴不是安全邊界
任何帶有 VITE_ 前綴的變數都會在建置時靜態內嵌到客戶端綑綁結果中。最小化、Base64 編碼或停用 source map 都無法隱藏它。有決心的攻擊者可以從已傳送的 JavaScript 中提取任何 VITE_ 變數。
規則: 僅將公開值(API 網址、功能旗標、公開金鑰)放入 VITE_ 變數中。機密(API 金鑰、資料庫網址、私密金鑰)必須存在於伺服器端,透過 API 或無伺服器函數提供服務。
loadEnv('') 陷阱
// 錯誤:將 '' 作為第三個參數傳遞會載入**所有**環境變數——包括伺服器機密——
// 並使其可透過 `define` 內嵌到客戶端程式碼中。
const env = loadEnv(mode, process.cwd(), '')
// 正確:明確的前綴列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
生產環境的 Source Map
生產環境的 source map 會泄漏你的原始原始碼。請停用它們,除非你上傳到錯誤追蹤服務(Sentry、Bugsnag)並在之後於本機刪除:
build: {
sourcemap: false, // 預設值——請保持此設定
}
.gitignore 檢查清單
.env.local、.env.*.local— 本機機密覆蓋dist/— 建置輸出node_modules/.vite— 預先打包快取(過時項目會導致幽靈錯誤)
伺服器代理
// vite.config.ts — server.proxy
server: {
proxy: {
'/foo': 'http://localhost:4567', // 字串簡寫
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // 虛擬主機後端需要此設定
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
若要代理 WebSocket,請在路由設定中新增 ws: true。
建置最佳化
手動 Chunk
// vite.config.ts — build.rolldownOptions
build: {
rolldownOptions: {
output: {
// 物件形式:分組特定套件
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
},
},
}
// 函數形式:根據啟發式分割
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor'
if (id.includes('node_modules')) return 'vendor'
}
效能
避免 Barrel 檔案
Barrel 檔案(重新導出目錄中所有內容的 index.ts)會迫使 Vite 載入每個重新導出的檔案,即使你只導入單一符號。這是官方文件指出的頭號開發伺服器效能瓶頸。
// 錯誤——導入一個工具函數會迫使 Vite 載入整個 barrel
import { slash } from '@/utils'
// 正確——直接導入,只載入一個檔案
import { slash } from '@/utils/slash'
明確指定導入副檔名
每個隱含副檔名會透過 resolve.extensions 強制進行最多 6 次檔案系統檢查。在大型程式碼庫中,這會累積起來。
// 錯誤
import Component from './Component'
// 正確
import Component from './Component.tsx'
請將 tsconfig.json 的 allowImportingTsExtensions 與 resolve.extensions 縮小到僅包含你實際使用的副檔名。
預熱熱門路徑
server.warmup.clientFiles 會在瀏覽器請求之前預先轉換已知的熱門進入點——消除大型應用程式的冷載入請求瀑布。
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
分析慢速開發伺服器
當 vite dev 感覺緩慢時,先執行 vite --profile,與應用程式互動,然後按下 p+enter 儲存 .cpuprofile。在 Speedscope 中載入,找出哪些外掛耗費時間——通常是社群外掛中的 buildStart、config 或 configResolved 鉤子。
程式庫模式
發行 npm 套件時,請使用 build.lib。有兩個陷阱比設定細節更重要:
- 型別不會被發出——請新增
vite-plugin-dts或單獨執行tsc --emitDeclarationOnly。 - 對等相依性必須外部化——未列出的對等相依性會被綑綁到你的程式庫中,導致消費端發生重複執行時期錯誤。
// vite.config.ts
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], // 每個對等相依性
},
}
SSR 外部化
單純的 createServer({ middlewareMode: true }) 設定是框架作者的地盤。大多數應用程式應使用 Nuxt、Remix、SvelteKit、Astro 或 TanStack Start。作為框架使用者,你可能需要調整的是當相依性在 SSR 中中斷時的外部化設定:
// vite.config.ts — ssr 選項
ssr: {
external: ['node-native-package'], // 在 SSR 綑綁結果中保留為 require()
noExternal: ['esm-only-package'], // 強制綑綁到 SSR 輸出(修復大多數 SSR 錯誤)
target: 'node', // 'node' 或 'webworker'
}
相依性預先打包
Vite 會預先打包相依性,以將 CJS/UMD 轉換為 ESM 並減少請求數量。
// vite.config.ts — optimizeDeps
optimizeDeps: {
include: [
'lodash-es', // 強制預先打包已知的沉重相依性
'cjs-package', // 導致互操作問題的 CJS 相依性
'deep-lib/components/**', // 深層導入的 glob 模式
],
exclude: ['local-esm-package'], // 如果排除,則必須是有效的 ESM
force: true, // 忽略快取,重新最佳化(臨時除錯用)
}
常見陷阱
開發環境與建置環境不一致
開發環境使用 esbuild/Rolldown 進行轉換;建置環境使用 Rolldown 進行綑綁。CJS 程式庫在兩者之間可能表現不同。在部署前,務必使用 vite build && vite preview 進行驗證。
部署後的過期 Chunk
新的建置會產生新的 chunk 雜湊。具有活躍工作階段的使用者會請求不再存在的舊檔案名稱。Vite 沒有內建的解決方案。緩解措施:
- 在部署時間窗口內保留舊的
dist/assets/檔案 - 在路由器中捕獲動態導入錯誤,並強制重新載入頁面
Docker 與容器
Vite 預設繫結到 localhost,這從容器外部無法訪問:
// vite.config.ts — Docker/容器設定
server: {
host: true, // 繫結 0.0.0.0
hmr: { clientPort: 3000 }, // 如果在反向代理後方
}
單一儲存庫檔案存取
Vite 將檔案提供限制在專案根目錄。根目錄外的套件會被封鎖:
// vite.config.ts — 單一儲存庫檔案存取
server: {
fs: {
allow: ['..'], // 允許父目錄(工作區根目錄)
},
}
反模式
// 錯誤:將 envPrefix 設為 '' 會將所有環境變數(包括機密)暴露給客戶端
envPrefix: ''
// 錯誤:假設 require() 在 Vite 前端模組中能正常運作(會導致執行時期錯誤)
const fs = require('fs')
// 錯誤:在依賴可能已變更後,未清除 node_modules/.vite 快取就重新啟動
// 請刪除 node_modules/.vite 或使用 optimizeDeps.force: true
// 錯誤:在生產建置中啟用 source map,但未上傳到錯誤追蹤服務
build: {
sourcemap: true
}