关键在于用 globalthis 结合环境特征统一判断执行环境:浏览器控制台(window && document)、node.js repl(process && module.parent === null)、node 模块(module.parent !== null)、esm(import.meta.url);再按需导出或执行。

要让一段 JavaScript 脚本在控制台(浏览器 DevTools)、Node.js REPL 和作为模块被 require 或 import 时都能正常运行,关键在于准确识别当前执行环境,并动态调整导出/输出行为。核心不是“写三套逻辑”,而是用统一代码、按需响应。
判断执行环境:靠 globalThis 与上下文特征
现代 JS 推荐优先使用 globalThis 统一访问全局对象,再结合环境特有属性做精准判断:
- 浏览器控制台:存在
globalThis.window且globalThis.document可访问 - Node.js REPL:存在
globalThis.process,且process.stdout存在,但module?.parent === null(REPL 中module.parent为null) - Node 模块脚本(被
require或import):存在module?.exports,且module.parent !== null - ESM 模块(
import场景):import.meta?.url存在,且globalThis.module === undefined
统一入口 + 条件导出:一份代码,多种暴露方式
不依赖外部构建工具,纯运行时决定行为。例如实现一个通用的 logWithEnv 工具函数:
使用一条命令部署ProbeChain Rydberg测试网代理节点。自动注册为Agent(NodeType=1),免gas,支持macOS/Linux/Windows。触发词:/r
// universal-tool.js
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
const isREPL = isNode && module?.parent === null;
const isModule = isNode && module?.parent !== null;
function logWithEnv(msg) {
if (isBrowser) {
console.log(`[Browser] ${msg}`);
} else if (isREPL) {
console.log(`[REPL] ${msg}`);
} else {
console.log(`[Module] ${msg}`);
}
}
// 浏览器:挂到 window 或直接执行
if (isBrowser) {
window.logWithEnv = logWithEnv;
// 可选:立即执行示例(仅在直接粘贴到控制台时生效)
if (document.currentScript === undefined) {
logWithEnv('Loaded in browser console');
}
}
// Node REPL:直接可用,无需导出
if (isREPL) {
logWithEnv('Running in Node REPL');
}
// CommonJS 模块(require)
if (isModule && typeof module !== 'undefined' && module.exports) {
module.exports = { logWithEnv };
}
// ESM 模块(import)——注意:需配合 .mjs 后缀或 package.json "type": "module"
if (isNode && typeof importMetaUrl !== 'undefined') {
export { logWithEnv };
}
处理 ESM 与 CJS 共存的现实约束
纯 ESM 环境(如 type: "module")中 module.exports 不可用;而 CJS 中 export 语法报错。稳妥做法是:
- 发布双版本:一个
.cjs(CommonJS),一个.mjs(ESM),由 package.json 的"main"和"exports"字段引导 - 单文件兼容方案:用
eval('export')等黑科技不推荐;更实用的是只暴露函数,由使用者按需包装 - 实际项目中,建议用
createRequire(import.meta.url)在 ESM 中加载 CJS,或用dynamic import()统一入口
验证与调试技巧
快速验证是否跨环境就绪:
- 浏览器控制台:复制粘贴脚本 → 检查是否定义了预期变量 / 是否打印提示
- Node REPL:
node回车后.load universal-tool.js→ 调用函数看输出 - 模块调用:
node -e "require('./universal-tool.js').logWithEnv('test')"或node -e "import('./universal-tool.mjs').then(m => m.logWithEnv('test'))" - 注意:ESM 动态 import 返回 Promise,REPL 中可用
await(需启用实验性标志或 Node ≥18.19)
不复杂但容易忽略:环境判断要早于任何副作用,导出逻辑不能阻塞执行,且避免污染全局命名空间(除非明确面向控制台场景)。真正“完美兼容”的脚本,本质是谦逊的——它知道自己在哪,也懂得适时退场或亮明身份。










