Vite 設定與運作原理

AdSense

Vite 是什麼

Vite 是一個以 ES Modules 為基礎、具熱更新 (HMR) 的前端開發工具。開發階段直接讓瀏覽器載入原始碼,不需要預先打包。發佈階段再透過 Rollup 或 Rolldown 打包成靜態檔案。

Vite 指令與執行模式

1. 常用指令

指令 功能 預設模式 (mode)
npm run devvite 啟動開發伺服器,支援即時熱更新 (HMR) development
npm run buildvite build 打包專案成靜態檔案 production
npm run previewvite preview 啟動一個本地伺服器預覽打包結果 production

2. npm run dev vs npm run preview

項目 npm run dev npm run preview
功能 啟動開發伺服器 (Vite Dev Server) 啟動預覽伺服器
模式 development production
資源載入 即時讀取原始碼 (src/main.ts) 載入打包後的靜態檔 (dist/)
是否需先打包 不需要 需要先執行 npm run build
用途 開發、即時更新 (Hot Module Replacement, HMR) 模擬正式部署

Vite 設定檔 vite.config.ts 基本結構

範例

1
2
3
4
5
6
7
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true
}
})

說明

  • defineConfig():Vite 官方提供的函式,提供型別提示與確保匯出的物件符合 Vite 設定格式
  • pluginsVite 本身只是一個通用的建構工具,並不「內建」React、Vue、Svelte 等框架支援。設定插件,例如 React,讓 Vite 知道要:
    • 如何編譯 .jsx.tsx 檔案。
    • 如何處理 React HMR (Hot Module Replacement)。
    • 如何解析 JSX 語法。
  • server.port:指定本地伺服器埠號。
  • server.open:啟動時自動開啟瀏覽器。

根據模式 mode 載入 .env

範例

1
2
3
4
5
6
7
8
9
10
11
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), 'VITE')
const port = parseInt(env.VITE_PORT ?? '5173', 10)

return {
plugins: [react()],
server: {
port
}
}
})
  • mode:決定要載入哪一組 .env.<mode> 檔。
  • process.cwd():回傳專案根目錄路徑。
  • 'VITE':只載入以 VITE_ 開頭的變數。
  • parseInt(..., 10):將字串轉為整數 (10 表十進位)。若 .env 中沒有 VITE_PORTparseInt(..., 10) 結果為 NaN,所以常加預設值。

mode 是什麼?從哪裡來?

當你執行 Vite 指令時,CLI 會呼叫一個 resolveConfig() 函式,在 resolveConfig() 裡面,Vite 會建立這個「上下文物件」,大致長這樣:

1
2
3
4
5
6
7
{
command: 'serve', // 或 'build',代表你是啟動還是打包
mode: 'development', // 或 'production',取決於你用哪個模式執行
isSsrBuild: false, // 是否為 SSR 模式
isPreview: false, // 是否為 preview 模式
ssrBuild: false
}

再將這個物件,傳進你的設定函式裡:

1
2
3
4
export default defineConfig(({ command, mode }) => {
console.log(command) // serve 或 build
console.log(mode) // development 或 production
})

指令對應到的 mode 類別

指令 預設 mode
vite / vite dev development
vite build production
vite preview production

可以透過 --mode 覆寫,此時 mode 會變成 'staging',並讀取 .env.staging

1
vite build --mode staging

.env 檔載入順序

Vite 根據 mode 依序載入 (後者會覆蓋前者):

  1. .env
  2. .env.local
  3. .env.[mode]
  4. .env.[mode].local

例:mode = 'development' 時載入順序:

1
.env → .env.local → .env.development → .env.development.local

若該檔案不存在,接跳過載入該檔案,並繼續下一個檔案。.env 一直存在且一直都被載入。

process.cwd()__dirname 差別

名稱 回傳 用途
process.cwd() 當前執行 Vite 指令的資料夾 要根據專案根目錄找資源時 (Vite 常用)
__dirname 當前檔案所在的資料夾 要根據檔案位置操作檔案時

Vite 使用 process.cwd(),是為了保證即使設定檔在子資料夾,也能找到專案根目錄

tsconfigPaths() 插件

1
2
3
4
5
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
plugins: [react(), tsconfigPaths()]
})

功能

自動讀取 tsconfig.json 裡的 paths 設定,讓 Vite 能識別 TypeScript 別名,例如:

1
2
3
"paths": {
"@components/*": ["src/components/*"]
}

若不使用插件

需手動設定 resolve.alias

1
2
3
4
5
6
7
8
export default defineConfig({
plugins: [react(), tsconfigPaths()],
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components')
}
}
})

base 與打包後資源路徑

base 用來控制 打包後資源路徑在 HTML 中的 URL 前綴

開發與打包階段的 index.html

開發時專案目錄下的 index.html

1
2
3
4
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

打包後結構:

1
2
3
4
5
6
project/
├─ dist/
│ ├─ assets/
│ │ ├─ index-COcDBgFa.css
│ │ └─ index-DLt9nNv6.js
│ └─ index.html

base: '/',打包後的 index.html

1
2
3
4
5
6
7
8
<head>
<title>first-vite</title>
<script type="module" crossorigin src="/assets/index-DLt9nNv6.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-COcDBgFa.css">
</head>
<body>
<div id="root"></div>
</body>

base: '/myapp/',打包後的 index.html

1
2
3
4
5
6
7
8
<head>
<title>first-vite</title>
<script type="module" crossorigin src="/myapp/assets/index-DLt9nNv6.js"></script>
<link rel="stylesheet" crossorigin href="/myapp/assets/index-COcDBgFa.css">
</head>
<body>
<div id="root"></div>
</body>

用來部屬在伺服器的子資料夾 myapp/ 裡面 (例如 GitHub Pages 或 Nginx 子站),需要將整個 dist/ 的內容放入該子資料夾中:

1
2
3
4
5
6
(伺服器根目錄)
└── myapp/
├── assets/
│ ├── index-COcDBgFa.css
│ └── DLt9nNv6.js
└─ index.html

base: '',打包後的 index.html

1
2
3
4
5
6
7
8
<head>
<title>first-vite</title>
<script type="module" crossorigin src="assets/index-DLt9nNv6.js"></script>
<link rel="stylesheet" crossorigin href="assets/index-COcDBgFa.css">
</head>
<body>
<div id="root"></div>
</body>

base: 'https://cdn.example.com/',打包後的 index.html

1
2
3
4
5
6
7
8
<head>
<title>first-vite</title>
<script type="module" crossorigin src="https://cdn.example.com/assets/index-DLt9nNv6.js"></script>
<link rel="stylesheet" crossorigin href="https://cdn.example.com/assets/index-COcDBgFa.css">
</head>
<body>
<div id="root"></div>
</body>

總結

base 打包後資源路徑 用途
'/' /assets/... 部署在根目錄。絕對路徑,從網站根目錄載入。
'/myapp/' /myapp/assets/... 部署在子資料夾。絕對路徑,從子目錄載入。
'' assets/... 相對路徑,根據 HTML 位置載入
'https://cdn.example.com/' https://cdn.example.com/assets/... CDN 載入

注意

若設定:

1
2
base: 'https://cdn.example.com/assets/',
build: { assetsDir: 'assets' }

打包結果會變成:

1
https://cdn.example.com/assets/assets/...

正確寫法:

1
2
3
4
5
6
// 方法 1
base: 'https://cdn.example.com/'

// 方法 2
base: 'https://cdn.example.com/assets/',
build: { assetsDir: '' }

完整設定範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), 'VITE')
const port = parseInt(env.VITE_PORT, 10)

return {
plugins: [react(), tsconfigPaths()],
base: '',
server: {
port: !Number.isNaN(port) ? port : 5001,
},
test: {
environment: 'jsdom', // 模擬瀏覽器環境
globals: true, // 允許使用全域的測試語法,如 describe, it, expect
setupFiles: './src/__tests__/setup.ts', // 測試前執行的設定檔 (常用來掛載 Mock、初始化)
outputFile: { // 測試報告輸出位置
junit: './project-client.junit.xml',
json: './project-client.report.json',
},
maxWorkers: 4, // 控制 Vitest 同時啟動的測試執行緒數量
minWorkers: 4, // 控制 Vitest 同時啟動的測試執行緒數量
},
}
})

重點整理:

主題 說明
mode 由 CLI 傳入 (development / production),可用 --mode 改變
loadEnv() 根據 mode 載入 .env
process.cwd() 回傳專案根目錄
tsconfigPaths() 讓 Vite 支援 TypeScript 別名
base 打包後資源的 URL 前綴
build.outDir 打包輸出資料夾
test Vitest 的設定
npm run dev 即時開發、HMR 更新
npm run preview 模擬正式部署結果