dev 期請求 mock(registerApiMock)
後端 API 還沒就緒,但你想先把頁面、元件、互動做完?swift-web 提供 registerApiMock:在 dev 期宣告「某個請求回傳什麼」,元件照常呼叫真實的 request(),等 API 好了,刪掉 mock 就能聯調。
這是一個輕量的 dev 期權宜工具,不是專業 mock 體系:
- 何時用:新專案後端 API 未就緒、或想純前端在本機把 UI / UX 跑起來。
- 何時別用:舊專案改造、後端 API 現成——不需要、別引入,
registerApiMock不呼叫就完全不存在(dev-only,生產建置會被刪淨)。 - 邊界:搞不定的個別場景,applier 自己在元件裡暫時寫死也行。這工具不追求覆蓋 100%。
一、原理:為什麼攔在請求層、為什麼不用第三方套件
swift-web 的 request() 把請求體 AES-CBC 動態金鑰加密 + gzip 後送出,回應也是密文。registerApiMock 攔在 createApiFetch 內部、加密之前:命中就回傳你給的明文 data、未命中就 passthrough 走真實後端。
這就是為什麼業界那些網路層 mock 套件(MSW / vite-plugin-mock / MirageJS)在這套架構裡不好用:
- 它們都攔在「網路線纜」那一層,那裡的請求 / 回應是密文——要 mock 就得在 handler 裡複刻一遍動態金鑰推導 + 加密,又繁又脆。
registerApiMock攔在明文層,handler 直接回傳普通物件,天然繞開加密。- 額外坑:MSW 在瀏覽器靠 Service Worker,會和 swift-web 的 PWA(workbox)service worker 衝突。
所以這裡自帶一個薄機制是架構決定的,不是重複造輪子。
二、快速上手
把所有 mock 集中寫在一個 dev-only 外掛裡。推薦用 universal plugin(不帶 .client 後綴)——dev 期 SSR + CSR 雙端攔截,ISR / SSR 頁在 setup 頂層 await useAsyncData(() => request(...)) 也能直出 mock 資料(SSR HTML 完整,頁面從第一天就是接後端的最終形態):
// app/plugins/dev-mock.ts
export default defineNuxtPlugin(() => {
registerApiMock('/movie/home', () => ({
list: [
{ id: 1, title: 'mock 影片一', cover: '/mock/1.jpg' },
{ id: 2, title: 'mock 影片二', cover: '/mock/2.jpg' },
],
}))
registerApiMock('/movie/detail', ({ body }) => ({
id: (body as { id: number }).id,
title: 'mock 詳情',
}))
})
元件 / composable 照常呼叫真實的 request(),完全不用知道有沒有 mock:
const home = await request('/movie/home')
dev 期命中 mock 時,主控台會印一行 [mock] SSR POST /movie/home(或 CSR,標註攔截發生在哪端),方便你確認攔截生效。SSR 命中的日誌出現在 dev server 終端機,CSR 命中的在瀏覽器主控台。
檔名仍寫成
dev-mock.client.ts也可以——那樣退回只攔 client 端(SSR 期照舊打真實後端),適合「後端已就緒、只想在瀏覽器裡臨時替換個別介面」的場景。
整合 API 時:把
app/plugins/dev-mock.ts刪掉就行——元件程式碼一行不用動,請求直接打到真後端。這就是「先做完 UI、後聯調」的乾淨切口。別忘了同一步刪掉request.mock設定段關停內建假資料——見 §七 關閉與移除 mock。
framework 自帶服務零配置(內建預設)
request.mock.enabled: true 時,framework 還會自動 mock 它自己的兩個服務,連寫都不用寫:
- 登入
/login/*→ 回傳內建假使用者,能直接進登入態; /system/info→ 注入預設 skeleton(SSR + CSR 都生效)。
覆蓋:用 registerApiMock('/login/username', …) 覆蓋內建登入;用 swift.config.json 的 request.mock.systemInfo 覆蓋 system/info 欄位。只想要機制、不要內建假資料時設 request.mock.builtins: false。
⚠️
builtins內建假資料只在enabled顯式寫true時啟用 ——enabled缺省時 dev 期雖然預設開(mock 機制可用、registerApiMock照常攔截),但內建預設不會啟用,防止劫持專案自己的 dev mock 資料。這是全 ModuleOptions 中唯一「來源敏感」的開關。
三、API 參考
registerApiMock(matcher, handler, options?)
註冊一條 mock,回傳一個註銷 handle(呼叫即移除這條)。
matcher(三態,決定哪個請求被攔)
| 形態 | 例子 | 說明 |
|---|---|---|
| 精確字串 | '/login/username' | 按 path 精確比對(忽略 url 上的 query 字串) |
| 物件 | { path: '/order/submit', method: 'POST' } | 可加 method / query 精細比對;省略 method 則任意方法命中 |
| 正則 | /^\/movie\/\d+$/ | 測 path |
優先級:精確字串 > 物件 > 正則;同一檔先註冊的先命中。
handler
(ctx: { url: string; method: string; body: unknown; query: Record<string, string> }) => data | Promise<data>
回傳明文 data——也就是 request<T>() 裡那個 T(框架內部已經幫你解包了 { status, data } 這層,你只管回傳 data)。可以是 async。
options
| 選項 | 作用 |
|---|---|
delay | 模擬網路延遲(ms),用來驗證 loading 態 |
status: 'n' | 模擬業務失敗,走和真實後端 status:'n' 一樣的失敗分支(拋錯) |
errorCode | 配合 status:'n';填 2002 會觸發框架的「靜默重登 + 重試一次」邏輯 |
error | status:'n' 時的錯誤文案,預設「請求失敗」 |
once | 命中一次後自動註銷 |
registerApiMock({ path: '/order/submit', method: 'POST' }, () => ({ orderId: 'x' }), { delay: 300 })
registerApiMock('/pay/confirm', () => ({}), { status: 'n', error: '餘額不足' })
clearApiMocks()
清空全部已註冊的 mock(寫測試 / 重置時用)。
整個 mock 層的開關(
request.mock.enabled/builtins)+ 建立期 / 既有專案兩種關法,統一見下方 §七 關閉與移除 mock。
四、登入怎麼 mock(重點)
沒後端時最常見的需求是「假裝登入成功」。別去 monkeypatch userStore.loginByUsername——那要懂 AES cookie、要重寫 store 內部,又脆又每個專案重造。
正確做法:mock 登入介面回傳一個假使用者即可。因為攔截在加密前、回傳明文 data,框架後續的 setInfo / 寫 cookie / isLoggedIn / 路由守衛全走真實邏輯:
// app/plugins/dev-mock.ts
registerApiMock('/login/username', ({ body }) => ({
token: 'mock-token',
user_id: '1',
nickname: '雲端漫步',
avatar: '/mock/avatar.jpg',
}))
這樣在登入頁 / 登入彈窗輸入任意帳號密碼,就能進到真實的「已登入」態,/mine 守衛放行、登出清態也正常。
五、安全模型(為什麼不會洩漏到生產)
假登入如果進了生產 = 「任意帳號密碼即登入」後門,是災難。registerApiMock 有三重兜底:
- dev-only:攔截邏輯包在
import.meta.env.DEV裡,生產建置時被 Vite DCE(dead-code elimination)整段刪除——程式碼根本不進生產 bundle。 - 雙端同一道門:SSR 端同樣只在 dev 生效——生產 server 建置裡
import.meta.env.DEV是靜態false(建置期消除;即便個別建置器保留程式碼,註冊與查表也都是執行期 no-op)。 - 總開關:
request.mock.enabled可顯式關。
你照常寫 mock 即可,不需要自己加「靠執行期開關擋的後門」那種舊寫法。
SSR 說明:mock 是否覆蓋 SSR 取決於外掛註冊在哪端——universal plugin(
dev-mock.ts)雙端都攔;.client.ts只攔 client。once計數在 SSR 端是 dev server 行程級的(任一請求命中一次即全域摘除)。system/info例外:它由內建預設在 SSR 期直接注入 skeleton(零請求、不受外掛時序影響,見上「內建預設」),無需你 mock。
六、和「資料層 composable」的關係
另一種常見的 dev mock 寫法是:每個功能寫一個 useXxx composable,裡面直接回傳靜態資料,等 API 好了再把函式體換成真實 request()。這種「資料層 seam」也完全可用,和 registerApiMock 互補:
registerApiMock:元件從第一天就呼叫真實的request(),mock 集中在一處、整合即刪;登入 / system-info 這類走框架機器的請求只能用它。- 資料層 composable:結構未知、想強型別的業務資料,寫在 composable 裡也清晰。
兩者按手感選,不衝突。
七、關閉與移除 mock
接入真後端:刪 mock 設定是 checklist 動作,不是可選項
全域 mock 是有生命週期的:鷹架期(後端不存在 / 未就緒)request.mock.enabled: true + 內建假資料完全正當;從接入真後端那一刻起,刪掉整個 request.mock 設定段——把它當作「接入真後端 checklist」的固定一條,動作固定、不靠人記得。
- 為什麼是「刪設定段」而不是改
enabled: false:顯式false不只關內建假資料,還會把registerApiMock的消費層整個關死——你的單點 mock 和依賴它的測試 fixture 一起失效。而刪掉設定段後恰好是想要的狀態:內建假資料停(它只在顯式enabled: true時啟用),registerApiMock機制保留(缺省 dev 預設開,無註冊 = 無副作用)。 - 之後仍需 mock 的個別端點:只用
registerApiMock單點註冊,且每個註冊項掛 issue 標註「臨時、待後端就緒即刪」——單點是顯式例外(有清單、一眼可數),全域開關是隱式掩蓋(什麼都兜、什麼都看不見)。 - 關掉後,dev 從此依賴後端可達。後端連不上的正解是修可達性(隧道 /
.env/ 網路)或臨時單點 mock + 掛 issue,不是把全域開關再打開。原先依賴內建假資料的測試要自補兩類 app-init 狀態:登入(fixture 註冊後補跑 autoLogin,走真實setInfo)與system/info(測試裡setInfo復刻)。 - 為什麼這麼嚴:全域 mock 與真後端並存時,「頁面能跑」在「API 正常」與「API 壞了被 mock 兜住」兩種假設下看起來一模一樣——協定漂移、後端異常都暴露不出來;內建假資料還會頂掉真實資料造出假狀態。真實案例:某站接入真後端後
enabled: true沒刪,/system/info的內建 skeleton(刻意最小、沒有menus)被直注進 store,頻道頁因menus恆空直接 500——後端端點齊全、框架無缺陷,問題一直藏在 mock 層,接入後才炸出。
五檔關法速查
從「專案級開關」到「徹底刪乾淨」分五檔,按需要選:
| 想做什麼 | 怎麼做 |
|---|---|
| 新專案根本不想要 mock(已有後端 / 舊專案改造) | sw create 時「啟用 dev mock 資料?」選否 —— CLI 直接寫 request.mock.enabled: false 並刪掉 starter 自帶的業務 mock 預設檔 |
| 接入真後端(聯調開始) | 刪掉整個 request.mock 設定段(見上節;別用 enabled: false——會連 registerApiMock 單點通道一起關死) |
| 既有專案暫時全關(dev 期想直接打真後端) | sw config set framework.request.mock.enabled false(或手改 swift.config.json)。注意:這會連內建的 /login/*、/system/info 假資料、以及你自己註冊的單點 mock 一起關,請求全部直連真後端 |
只關內建假資料、保留 registerApiMock 機制 | request.mock.builtins: false —— 機制照常攔你自己註冊的 mock,只是不再自帶 login / system-info |
| API 好了,徹底移除業務 mock | 刪掉 app/plugins/dev-mock.ts(或對應的 mock 註冊)。元件 / composable 裡的 request() 呼叫一行不用改,直接打真後端 |
request.mock.enabled預設隨import.meta.env.DEV(dev 開 / prod 關,且 prod 還會被 DCE 整段刪淨),所以大多數情況你不用主動關——上表是「想提前關 / 改造舊專案 / 進入聯調」時才需要的開關。
整合聯調時,刪掉 mock 檔後還有一步:核對真實回應結構與你 mock 時假設的欄位是否一致,按需調整欄位對映。因為後端是固定框架、各專案 API 大體同構,這步通常只是微調欄位,不會推倒重來。
相關連結
- Module Options ·
request.mock——enabled/builtins/systemInfo設定欄位速查 - 快速上手 · CLI 命令參考 ——
sw create「啟用 dev mock 資料?」與sw config set的完整用法 - 自動登入 + Token 重登鏈路 —— mock 登入介面後,框架真實跑的就是這條鏈路(
setInfo/ 寫 cookie / 守衛) - 客戶端加密的安全邊界 —— 為什麼請求體是密文、mock 為何要攔在「明文層」