5月28日 03:16

如何判断 JS 文件是 Node.js 环境还是浏览器环境?

看三个层面:模块语法、全局对象、环境 API,基本够用。

模块语法是最直观的线索——用了 require/module.exports 的基本是 Node.js(CommonJS),但这不是充分条件,因为浏览器端打包工具也能处理 CJS。反过来,纯 import/export(ESM)两边都能跑,不能用来判断。

全局对象更可靠:访问 process__dirname__filenameglobal 的是 Node.js;访问 windowdocumentnavigatorlocalStorage 的是浏览器。但要注意,SSR 框架(Next.js)里两者可能同时存在。

环境 API 是最终判据:调了 fschild_processnetcrypto(非 Web Crypto 子集)等 Node 核心模块的只能在 Node 跑;用了 DOM API(document.querySelectoraddEventListener)、WebSocketWebRTC 的只能在浏览器跑。

一个实用的判断函数:

js
function 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.jstarget 也能反推这个文件的预期运行环境。

TypeScript 怎么区分这两种环境的类型?

tsconfig.json"lib": ["DOM"] 会注入浏览器类型(documentwindow),不加就没有。"types": ["node"] 会注入 Node 类型(process__dirname)。编译时 TS 就能帮你揪出混用的情况——比如在 lib 不含 DOM 的配置下写了 document.getElementById,会直接报类型错误。

实际项目中踩过什么坑?

Next.js 里最常见——组件里直接用 window 做判断,SSR 阶段 window 不存在就炸了。正确做法是把浏览器 API 调用放进 useEffecttypeof window !== 'undefined' 守卫里。另一个坑:库的 package.json 没配 exports 字段,Node 和浏览器拿到同一个入口文件,结果浏览器端 import 了 Node 模块直接白屏。

标签:JavaScriptNodeJS