IS_CLIENT
用戶端環境判定常數,值為 typeof window !== 'undefined' && typeof document !== 'undefined'。framework runtime 內部統一用它替代 import.meta.client,applier 可選 import 使用。
為什麼存在
Nuxt 注入的 import.meta.client 在三個環境下的行為:
| 環境 | import.meta.client | IS_CLIENT |
|---|---|---|
| 瀏覽器 build | true(vite 靜態替換 + DCE) | true |
| server build | false | false |
| vitest 4.x | undefined(vite define 注入在 Rolldown backend 下不生效) | true(happy-dom 有 window) |
vitest 環境讀到 undefined,會讓所有 if (!import.meta.client) return 的主邏輯分支永遠走 return 兜底 —— composable 主分支無法被單測覆蓋。IS_CLIENT 用執行期 typeof window 判斷繞開這個限制。
使用範例
SSR/CSR 分流可視化(hydration 前後對照)
加载中...
import { IS_CLIENT } from '@swift-web/framework'
if (IS_CLIENT) {
// 用戶端邏輯:存取 window / localStorage / IndexedDB 等
const plain = decryptBinary(buf, imgKey)
}
業務側兩種寫法都可:Nuxt 官方 import.meta.client 仍是主流;寫自己的 composable 單測撞到 vitest 4.x define 限制時,換 IS_CLIENT 即可。
與 import.meta.server / import.meta.dev 的邊界
| 標誌 | 是否可用 IS_CLIENT 替代 | 原因 |
|---|---|---|
import.meta.client | ✅ | IS_CLIENT 即其可單測等價物 |
import.meta.server | ❌ 保留 | Nitro SSR 期語義綁定,typeof window 等價但有細微差別(server context 可能有 window polyfill) |
import.meta.dev | ❌ 必須保留 | 是 vite build 期 DCE 標誌,讓 prod build 刪除 dev-only 程式碼;換成執行期判斷會破壞 DCE |
注意事項
- framework runtime 內部一律用
IS_CLIENT,不要用import.meta.client—— 後者在 vitest 4.x 下是undefined(vite define 注入在 Rolldown backend 不生效),會讓 runtime 裡所有if (!import.meta.client) return的主邏輯永遠走兜底 return,useTheme/useModal/useAddToHomescreen等 composable 的主分支根本測不到。本 helper 用typeof window+typeof document雙判,happy-dom 與真實瀏覽器都為true、Node SSR 為false。 - applier 端不強推 —— 業務程式碼用 Nuxt 官方的
import.meta.client完全可以;只有在寫自己的 composable 單測、同樣撞上 vitest 4.x 這個限制時,才值得改用本 helper(已註冊 auto-import)。 import.meta.server/import.meta.dev不要一併替換掉 ——import.meta.server是 Nitro SSR 期語意繫結(與typeof window有細微差別);import.meta.dev是 vite 建置期 DCE 標誌,換成執行期 helper 會讓 prod build 無法刪除 dev-only 程式碼。