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 代码。