我有一个脚本,我正在从Node.js脚本中引用它,我希望保持JavaScript引擎的独立性。
例如,我希望只在运行在Node.js下时执行exports.x = y;
。如何进行此测试?
发表这个问题时,我不知道Node.js模块功能是基于CommonJS的。
对于我提供的具体示例,一个更准确的问题应该是:
脚本如何确定它是否被作为CommonJS模块引用?
我有一个脚本,我正在从Node.js脚本中引用它,我希望保持JavaScript引擎的独立性。
例如,我希望只在运行在Node.js下时执行exports.x = y;
。如何进行此测试?
发表这个问题时,我不知道Node.js模块功能是基于CommonJS的。
对于我提供的具体示例,一个更准确的问题应该是:
脚本如何确定它是否被作为CommonJS模块引用?
由于每个网站都可以轻松地声明相同的变量,因此没有可靠的方法来检测在Node.js中运行。然而,在Node.js中默认没有window
对象,因此您可以采用另一种方式,并检查是否正在浏览器内部运行。
这是我为应该在浏览器和Node.js下工作的库使用的方法:
if (typeof window === 'undefined') {
exports.foo = {};
} else {
window.foo = {};
}
如果在Node.js中定义了window
,它仍然可能爆炸,但没有充分的理由让某人这样做,因为您必须明确地省略var
或在global
对象上设置该属性。
编辑
要检测您的脚本是否作为CommonJS模块被引入,这是不容易的。唯一的共同点是:A:通过调用函数require
来包含模块;B:模块通过exports
对象的属性导出东西。现在如何实现取决于底层系统。Node.js将模块的内容包装在一个匿名函数中:
function (exports, require, module, __filename, __dirname) {
参考:https://github.com/ry/node/blob/master/src/node.js#L325
但不要尝试通过一些疯狂的arguments.callee.toString()
代码来检测,而是使用上面我提供的示例代码来检查浏览器。Node.js环境更加清洁,所以在那里声明window
的可能性很小。
window
,否则你不应该有任何问题。你也可以运行一个匿名函数并检查其中的this
的[[Class]]
(仅适用于非严格模式)。请参见“Class”:http://bonsaiden.github.com/JavaScript-Garden/#typeof - Ivo Wetzel通过寻找 CommonJS 支持,这就是 Underscore.js 库的实现方法:
编辑:对于您更新后的问题:
(function () {
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Create a reference to this
var _ = new Object();
var isNode = false;
// Export the Underscore object for **CommonJS**, with backwards-compatibility
// for the old `require()` API. If we're not in CommonJS, add `_` to the
// global object.
if (typeof module !== 'undefined' && module.exports) {
module.exports = _;
root._ = _;
isNode = true;
} else {
root._ = _;
}
})();
这里的示例保留了模块模式。
(typeof process !== 'undefined') && (process.release.name === 'node')
process.release
包含与当前[Node-]发布相关的元数据。process.release.name
的值也可能变为io.js
。
Bun旨在实现完全的Node.js兼容性,因此模仿了Node的行为。(typeof process !== 'undefined') &&
(process.release.name.search(/node|io.js/) !== -1)
(typeof process !== 'undefined') &&
(typeof process.versions.node !== 'undefined')
(typeof process !== 'undefined') && process.versions.bun)
(typeof process !== 'undefined') && process.isBun)
process
和process.version
存在于捆绑包中,因此我添加了一个额外的检查来检查process.version
,其中客户端上的process.release.node
未定义,但服务器端具有节点版本作为值。 - Aaronprocess.version
变量的任何定义(在React、Webpack或React-Webpack中)。我会很感激如果你能提供任何关于版本变量定义的提示,以便我可以将其添加到答案中。这取决于发布节点对Node >= 3.x.x的限制。 - Florian Neumannfunction isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
- brillout是一个非常简洁的测试,如果你使用
"types": ["bun-types"]`,它也会让TypeScript感到满意。 - undefined尝试确定代码运行环境的问题在于任何对象都可以被修改和声明,这使得几乎不可能弄清楚哪些对象是原生环境中的,哪些是程序修改过的。
然而,我们可以使用一些技巧来确定当前运行的环境。
让我们从 underscore 库中通常采用的解决方案开始:
typeof module !== 'undefined' && module.exports
这种技巧在服务器端实际上非常好用,因为当调用 require
函数时,它会将 this
对象重置为空对象,然后为您重新定义 module
,这意味着您不必担心外部修改。只要使用 require
加载代码,就是安全的。
然而,在浏览器端,它会失效,因为任何人都可以轻松地定义 module
以使其看起来像您寻找的对象。这可能是您想要的行为,但它也决定了库用户可以在全局作用域中使用的变量。也许有人想使用名称为 module
的变量,其中包含其他用途的 exports
。虽然这很不可能,但我们无法判断别人可以使用哪些变量,只因为另一个环境使用了这个变量名。
然而,技巧在于,如果我们假设您的脚本正在全局作用域中加载(如果通过script标签加载,它确实是如此),则变量不能被保留在外部闭包中,因为浏览器不允许那样。请记住,在 Node 中,this
对象是一个空对象,但 module
变量仍然可用,因为它是在外部闭包中声明的。所以,我们可以通过添加额外的检查来修复 underscore 的检查:
this.module !== module
通过这样做,如果有人在浏览器的全局范围内声明module
,它将被放置在this
对象中,这将导致测试失败,因为this.module
将是与module
相同的对象。在Node中,this.module
不存在,而module
存在于外部闭包中,因此测试将成功,因为它们不等价。
因此,最终的测试为:
typeof module !== 'undefined' && this.module !== module
注意:虽然现在允许在全局范围内自由使用module
变量,但在浏览器上仍然可能绕过这一点,方法是创建一个新的闭包并在其中声明module
,然后在该闭包中加载脚本。此时,用户完全复制了Node环境,并希望知道自己在做什么并尝试进行Node风格的require。如果代码在脚本标签中调用,则仍将安全。
if(typeof process === 'object' && process + '' === '[object process]'){
// is node
}
else{
// not node
}
砰。
process+''
而不是 process.toString()
有什么特别的原因吗? - harmicObject.prototype.toString.call(process)
。 - sospedravar process = null;
,第二种情况将失败。在JavaScript和Java中,表达式'' + x
产生的结果与x.toString()
相同,除非 x
是有问题的,前者会产生"null"
或"undefined"
的字符串,而后者会抛出一个错误。 - joeytwiddlevar process = { toString: function () { return '[object process]'; } };
时,使用 Object.prototype.toString.call(process)
而不是 process + ''
是最佳解决方案。 - Michał Pietraszko以下也是一种相当不错的方法:
const isBrowser = this.window === this;
这是因为在浏览器中,全局的'this'变量有一个自我引用叫做'window'。但是在Node中,这个自我引用是不存在的。
要打破上述建议的浏览器检查,您需要执行以下操作:
this.window = this;
在执行检查之前。
const isBrowser = this.window !== undefined
呢?理论上,在 Node 中,我可以执行 this.window = this
来欺骗这个解决方案。 - Tyler Liu另一种 环境检测:
(意思是:这里的大部分答案都可以。)
function isNode() {
return typeof global === 'object'
&& String(global) === '[object global]'
&& typeof process === 'object'
&& String(process) === '[object process]'
&& global === global.GLOBAL // circular ref
// process.release.name cannot be altered, unlike process.title
&& /node|io\.js/.test(process.release.name)
&& typeof setImmediate === 'function'
&& setImmediate.length === 4
&& typeof __dirname === 'string'
&& Should I go on ?..
}
有点偏执,是吗?您可以检查更多的全局对象来使其更加详细。
以上所有内容都可以被伪造/模拟。
例如,要伪造global
对象:
global = {
toString: function () {
return '[object global]';
},
GLOBAL: global,
setImmediate: function (a, b, c, d) {}
};
setImmediate = function (a, b, c, d) {};
...
这不会被附加到Node的原始全局对象上,但它将被附加到浏览器中的window
对象上。因此,这将意味着你在浏览器内部的Node环境中。
如果我们的环境被伪造了,我们会在意吗?当一些愚蠢的开发者在全局作用域中声明一个名为global
的全局变量时,就会发生这种情况。或者一些邪恶的开发者以某种方式注入代码到我们的环境中。
我们可以在捕获到这种情况时防止我们的代码执行,但是我们应用程序的许多其他依赖关系可能会陷入这种情况。因此,最终代码将会出错。如果你的代码足够好,你不应该为别人可能犯的每一个愚蠢错误而担忧。
如果针对两个环境:浏览器和Node;
"use strict"
; 并检查 window
或 global
;并在文档中明确说明你的代码仅支持这些环境。就这样!
var isBrowser = typeof window !== 'undefined'
&& ({}).toString.call(window) === '[object Window]';
var isNode = typeof global !== "undefined"
&& ({}).toString.call(global) === '[object global]';
function isPromiseSupported() {
var supported = false;
try {
var p = new Promise(function (res, rej) {});
supported = true;
} catch (e) {}
return supported;
}
一行代码可以在现代JavaScript运行时中使用。
const runtime = globalThis.process?.release?.name || 'not node'
runtime
的值将是node
或not node
。
这取决于一些较新的JavaScript功能。 globalThis
在ECMAScript 2020规范中得以确定。 可选链接/空值合并(?
部分)在V8引擎的8.x+版本中受到支持(该版本已在Chrome 80和Node 14中发布,于2020年4月21日发布)。
大多数提出的解决方案实际上是可以伪造的。一种健壮的方式是使用Object.prototype.toString
检查全局对象的内部Class
属性。在JavaScript中,内部类无法伪造:
var isNode =
typeof global !== "undefined" &&
{}.toString.call(global) == '[object global]';
Object.prototype.toString
,这是非常糟糕的做法。 - Fabian Jakobsvar global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
- Vanuan({}.toString.call(window))
等于"[object global]"
。 - Vanuanwindow.toString()
会产生 "[object Window]"
。 - Vanuan使用进程对象并检查execPath是否为node
怎么样?
process.execPath
这是启动进程的可执行文件的绝对路径名。
例如:
/usr/local/bin/node
window.process = {execPath: "/usr/local/bin/node"};
的意思是什么? - Константин Ван