IS_CLIENT

用戶端環境判定常數,值為 typeof window !== 'undefined' && typeof document !== 'undefined'。framework runtime 內部統一用它替代 import.meta.client,applier 可選 import 使用。

為什麼存在

Nuxt 注入的 import.meta.client 在三個環境下的行為:

環境import.meta.clientIS_CLIENT
瀏覽器 buildtrue(vite 靜態替換 + DCE)true
server buildfalsefalse
vitest 4.xundefined(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 程式碼。