Vite 模式 是做什麼的?
適用於 Vite 8+ 專案的建置工具與開發伺服器模式。涵蓋設定檔配置、環境變數、代理設定、函式庫模式、依賴預建置,以及常見的生產環境陷阱。
使用時機
- 配置
vite.config.ts或vite.config.js - 設定環境變數或
.env檔案 - 為 API 後端配置開發伺服器代理
- 最佳化建置輸出(區塊、最小化、靜態資源)
- 使用
build.lib發布函式庫 - 疑難排解依賴預建置或 CJS/ESM 互通性問題
- 除錯 HMR、開發伺服器或建置錯誤
- 選擇或排序 Vite 外掛程式
運作原理
- 開發模式將原始檔案以原生 ESM 形式提供服務——無需打包。轉換按需求針對每個模組請求進行,這就是為什麼冷啟動快速且 HMR 精確。
- 建置模式使用 Rolldown (v7+) 或 Rollup (v5–v6) 為生產環境打包應用程式,並進行樹狀搖晃、程式碼分割,以及基於 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 檔案會被 git 忽略,用於存放本機機密。
客戶端存取
只有 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 // 布林值
在設定檔中使用 Env
// 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 編碼和停用原始碼映射都無法隱藏它。一個確定的攻擊者可以從已發布的 JavaScript 中提取任何 VITE_ 變數。
規則: 只有公開值(API URL、功能旗標、公開金鑰)放入 VITE_ 變數中。機密(API 金鑰、資料庫 URL、私密密鑰)必須僅存在於伺服器端,通過 API 或無伺服器函式提供。
loadEnv('') 陷阱
// 錯誤:傳遞 '' 作為第三個參數會載入**所有**環境變數——包括伺服器機密——
// 並使其可透過 `define` 內聯到客戶端程式碼中。
const env = loadEnv(mode, process.cwd(), '')
// 正確:明確的前綴列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
生產環境中的原始碼映射
生產環境中的原始碼映射會洩露你的原始程式碼。除非你將其上傳到錯誤追蹤工具(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。
建置最佳化
手動區塊
// 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 進行驗證。
部署後區塊過時
新的建置會產生新的區塊雜湊。活躍的工作階段會請求已不存在的舊檔案名稱。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() 在……
…[為翻譯而截斷]