Mock → 真實 API 漸進切換
app-web 支援從 Mock API 漸進式切換到真實 API,確保開發過程中系統始終可運作。
設計理念
| 階段 | 說明 | 適用情境 |
|---|---|---|
| Phase 1 | 全部使用 Mock API | 前端開發初期、後端尚未就緒 |
| Phase 2 | 混合模式 | 後端逐步完成,逐模組切換 |
| Phase 3 | 全部使用真實 API | 生產環境部署 |
MSW 控制機制
MSW (Mock Service Worker) 透過 config.app.msw.enabled 控制,根據 Vite 環境自動判斷。
配置方式
src/conf/config.ts
export const config: AppEnvironConfig = {
app: {
name: 'App',
version: '1.0.0',
stage: import.meta.env.MODE,
basename: '/',
baseURL: '/',
msw: {
enabled: import.meta.env.DEV, // 開發環境自動啟用
},
},
// ...
}
環境判斷
| 環境 | import.meta.env.DEV | MSW 預設狀態 | 說明 |
|---|---|---|---|
開發環境(npm run dev) | true | 啟用 | 自動攔截 API 請求 |
生產環境(npm run build) | false | 關閉 | 所有請求直接發送到後端 |
生產環境覆蓋(測試用)
在特殊情況下(如 E2E 測試),可透過後端 /config endpoint 在生產環境啟用 MSW:
// GET /config 回應
{
"app": {
"msw": {
"enabled": true
}
}
}
MSW 跨域攔截機制
問題背景
在混合模式(Phase 2)下,前後端可能運行在不同的 origin:
| 環境 | app-web | app-server |
|---|---|---|
| 本地開發 | http://localhost:3000 | http://localhost:8080/app-server |
| SIT 環境 | http://localhost:3000 | https://sitapp.leandev.io/app-server |
當 apiClient.baseURL 設定為跨域 URL 時,傳統的相對路徑 MSW handler 無法攔截:
// ❌ 相對路徑 handler 無法攔截跨域請求
const API_BASE = '/api/v1';
http.post(`${API_BASE}/auth/login`, handler);
// 請求:http://localhost:8080/app-server/api/v1/auth/login
// Handler:/api/v1/auth/login
// 結果:無法匹配!
萬用字元解決方案
所有 MSW handlers 使用萬用字元 * 匹配任意 origin:
src/mocks/handlers/auth.ts
// ✅ 萬用字元匹配任意 origin
const API_BASE = '*/api/v1';
export const authHandlers = [
http.post(`${API_BASE}/auth/login`, handler),
http.get(`${API_BASE}/auth/me`, handler),
];
匹配行為
萬用字元 * 只影響 origin 部分,路徑匹配依然精確:
Handler: */api/v1/auth/login
✅ /api/v1/auth/login (同源)
✅ http://localhost:8080/app-server/api/v1/auth/login (本地後端)
✅ https://sitapp.leandev.io/app-server/api/v1/auth/login(遠端環境)
❌ /api/v1/orders (路徑不同)
❌ /api/v2/auth/login (版本不同)
設計優勢
| 優勢 | 說明 |
|---|---|
| 零額外配置 | 不需要 Vite Proxy 或環境變數 |
| 環境無關 | 無論 baseURL 設定為何,都能正確攔截 |
| 精確匹配 | 萬用字元只影響 origin,路徑依然精確 |
| 簡化切換 | 混合模式下,只需移除 handler 即可切換到真實 API |
攔截流程圖
三階段切換流程
Phase 1:全部使用 Mock API
適用於前端開發初期,後端尚未就緒。
┌─────────────────────────────────────────────────┐
│ app-web + MSW │
│ │
│ npm run dev │
│ → MSW 自動啟用 │
│ → 所有 API 請求由 MSW handlers 處理 │
└─────────────────────────────────────────────────┘
開發指令:
cd app-web
npm run dev
驗證 MSW 啟動:
開啟瀏覽器 DevTools Console,應看到:
[MSW] Mock Service Worker started
[MSW] Available endpoints:
- POST /api/v1/auth/login
- GET /api/v1/auth/me
- GET /api/v1/products
- GET /api/v1/orders
- GET /api/v1/customers
Phase 2:混合模式
後端逐步完成 API 實作,前端逐模組切換到真實 API。
┌─────────────────────────────────────────────────┐
│ app-web + 混合模式 │
│ │
│ 認證 API → 真實(app-server 已實作) │
│ 訂單 API → 真實(app-server 已實作) │
│ 客戶 API → Mock(app-server 尚未實作) │
│ │
│ MSW 設定 onUnhandledRequest: 'bypass' │
│ → 未匹配的請求自動轉發到真實後端 │
└─────────────────────────────────────────────────┘
關鍵配置:
src/App.tsx
await worker.start({
onUnhandledRequest: 'bypass', // 未匹配的請求繼續通過到真實伺服器
})
切換方式:
移除對應的 MSW handler,該 API 就會自動轉發到真實後端。
例如,當 app-server 完成認證 API 後:
src/mocks/handlers/index.ts
import { authHandlers } from './auth' // 移除此行
import { orderHandlers } from './orders' // 移除此行
import { customerHandlers } from './customers'
export const handlers = [
// ...authHandlers, // 註解或移除:使用真實 API
// ...orderHandlers, // 註解或移除:使用真實 API
...customerHandlers, // 保留:繼續使用 Mock
]
Phase 3:全部使用真實 API
所有後端 API 就緒,準備生產部署。
生產環境必要模組
生產環境部署需要完整架構:
- app-web:前端 SPA
- app-server:後端 RESTful API
- app-web-host:將 SPA 封裝成 WAR 部署到 Java Application Server
┌─────────────────────────────────────────────────┐
│ app-web + app-server + app-web-host │
│ │
│ npm run build → 產出靜態資源 │
│ → 複製到 app-web-host │
│ → 打包成 WAR 部署 │
│ → 所有 API 連接 app-server │
└─────────────────────────────────────────────────┘
部署指令:
cd app-web
npm run build
# 建置產出複製到 app-web-host
cd ../app-web-host
./gradlew build
# 產出:build/libs/app-web-host.war
開發環境設定
同時啟動前後端
開發時可同時啟動 app-web 和 app-server:
# Terminal 1:啟動後端
cd app-server
./gradlew bootRun
# Terminal 2:啟動前端
cd app-web
npm run dev
配置後端 URL
透過環境配置指定後端位置。MSW 萬用字元設計讓你無需額外的 Vite Proxy 配置:
src/conf/config.ts
export const config: AppEnvironConfig = {
app: {
// 本地後端
baseURL: 'http://localhost:8080/app-server',
// 或遠端環境
// baseURL: 'https://sitapp.leandev.io/app-server',
msw: {
enabled: true, // 混合模式:MSW + 真實後端
},
},
// ...
}
為什麼不需要 Vite Proxy?
由於 MSW handlers 使用萬用字元 */api/v1,無論 baseURL 設定為何,MSW 都能正確攔截。未匹配的請求會直接 bypass 到 baseURL 指定的後端,不需要額外的 proxy 層。
混合模式開發流程
┌─────────────────────────────────────────────────────────────┐
│ 混合模式開發 │
│ │
│ 1. 設定 baseURL 指向後端(本地或遠端) │
│ 2. 保持 msw.enabled = true │
│ 3. 移除已完成的 handler → 請求自動轉發到真實後端 │
│ 4. 保留未完成的 handler → 繼續使用 Mock │
└─────────────────────────────────────────────────────────────┘