如何判断 JS 文件是 Node.js 环境还是浏览器环境?
看三个层面:模块语法、全局对象、环境 API,基本够用。
模块语法是最直观的线索——用了 require/module.exports 的基本是 Node.js(CommonJS),但这不是充分条件,因为浏览器端打包工具也能处理 CJS。反过来,纯 import/export(ESM)两边都能跑,不能用来判断。
全局对象更可靠:访问 process、__dirname、__filename、global 的是 Node.js;访问 window、document、navigator、localStorage 的是浏览器。但要注意,SSR 框架(Next.js)里两者可能同时存在。
环境 API 是最终判据:调了 fs、child_process、net、crypto(非 Web Crypto 子集)等 Node 核心模块的只能在 Node 跑;用了 DOM API(document.querySelector、addEventListener)、WebSocket、WebRTC 的只能在浏览器跑。
一个实用的判断函数:
jsfunction detectEnv() { if (typeof process !== 'undefined' && process.versions?.node) return 'node'; if (typeof window !== 'undefined' && typeof document !== 'undefined') return 'browser'; return 'unknown'; }
这个函数够用但不完美——Web Worker 里有 self 没有 window,Electron 里两个都有。
追问
Webpack 的 target 配置和这个问题有什么关系?
target: 'node' 时 Webpack 不会 polyfill fs/path 等 Node 模块,target: 'web' 时会。如果源码用了 Node API 但打包目标是浏览器,构建会报错或打出空模块。所以看 webpack.config.js 的 target 也能反推这个文件的预期运行环境。
TypeScript 怎么区分这两种环境的类型?
tsconfig.json 里 "lib": ["DOM"] 会注入浏览器类型(document、window),不加就没有。"types": ["node"] 会注入 Node 类型(process、__dirname)。编译时 TS 就能帮你揪出混用的情况——比如在 lib 不含 DOM 的配置下写了 document.getElementById,会直接报类型错误。
实际项目中踩过什么坑?
Next.js 里最常见——组件里直接用 window 做判断,SSR 阶段 window 不存在就炸了。正确做法是把浏览器 API 调用放进 useEffect 或 typeof window !== 'undefined' 守卫里。另一个坑:库的 package.json 没配 exports 字段,Node 和浏览器拿到同一个入口文件,结果浏览器端 import 了 Node 模块直接白屏。