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