我有一个Node.js脚本所需的脚本,我想保持JavaScript引擎独立。
例如,我只想exports.x = y;
在Node.js下运行。如何执行此测试?
发布此问题时,我不知道Node.js模块功能是否基于CommonJS。
对于我给出的具体示例,一个更准确的问题是:
脚本如何判断是否已将其作为CommonJS模块使用?
我有一个Node.js脚本所需的脚本,我想保持JavaScript引擎独立。
例如,我只想exports.x = y;
在Node.js下运行。如何执行此测试?
发布此问题时,我不知道Node.js模块功能是否基于CommonJS。
对于我给出的具体示例,一个更准确的问题是:
脚本如何判断是否已将其作为CommonJS模块使用?
Answers:
通过寻求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._ = _;
}
})();
这里的示例保留了Module模式。
嗯,没有可靠的方法来检测Node.js中的运行情况,因为每个网站都可以轻松声明相同的变量,但是,由于window
默认情况下Node.js中没有对象,因此您可以采用另一种方法来检查您是否正在内部运行浏览器。
这是我用于应该在浏览器和Node.js下均可使用的库的内容:
if (typeof window === 'undefined') {
exports.foo = {};
} else {
window.foo = {};
}
万一window
在Node.js中定义了它,它可能仍会爆炸,但是没有足够的理由让别人这样做,因为您将明确需要省去var
或设置global
对象的属性。
编辑
为了检测是否需要脚本作为CommonJS模块,这又不容易。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
在模块的第一行中那样导入内容,否则应该不会有任何问题。您也可以运行一个匿名函数,并检查[[Class]]
的this
里面(仅适用于非严格模式)请参阅“类”下:bonsaiden.github.com/JavaScript-Garden/#typeof
typeof self === 'object'
可能会更安全,因为typeof window === 'undefined'
在Web worker范围上失败。
我目前偶然发现了一个错误的Node检测,由于误导特征检测,该错误不知道Electron中的Node环境。以下解决方案可明确标识过程环境。
(typeof process !== 'undefined') && (process.release.name === 'node')
这将发现您是否在节点进程中运行,因为它process.release
包含“与当前[Node-]版本相关的元数据”。
在io.js产生之后,的值process.release.name
也可能变为io.js
(请参阅process-doc)。为了正确检测准备就绪的节点环境,我猜您应该检查如下:
(typeof process !== 'undefined') &&
(process.release.name.search(/node|io.js/) !== -1)
该语句已在Node 5.5.0,Electron 0.36.9(带有Node 5.1.1)和Chrome 48.0.2564.116中进行了测试。
(typeof process !== 'undefined') &&
(typeof process.versions.node !== 'undefined')
@daluege的评论激发了我思考一个更一般的证明。这应该从Node.js> = 0.10开始工作。我没有找到先前版本的唯一标识符。
附:我将这个答案发布在这里,因为这个问题将我引到了这里,尽管OP正在寻找另一个问题的答案。
process
并process.version
在包内存在,所以我增加了一个额外的检查process.version
,其中process.release.node
在客户端上是不明确的,但有一个节点版本服务器端的值
process.version
变量的任何定义(在react,webpack或react-webpack中)。我会很高兴在定义版本变量以将其添加到答案的任何提示。取决于release.node对节点> = 3.xx的约束
function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
试图弄清楚您的代码在哪个环境中运行的问题是,可以修改和声明任何对象,从而几乎不可能弄清哪些对象是环境的本机对象,以及哪些对象已被程序修改。
但是,我们可以使用一些技巧来确定您所处的环境。
让我们从下划线库中使用的公认解决方案开始:
typeof module !== 'undefined' && module.exports
这种技术实际上对于服务器端来说是完美的,因为require
调用函数时,它将this
对象重置为空对象,然后module
再次为您重新定义,这意味着您不必担心任何外部篡改。只要您的代码中加载了require
,您都是安全的。
但是,这在浏览器上是分散的,因为任何人都可以轻松定义module
以使其看起来像是您要查找的对象。一方面,这可能是您想要的行为,但同时也决定了库用户可以在全局范围内使用哪些变量。也许有人想将变量名称module
包含exports
在其中的变量用于其他用途。不太可能,但是我们是谁来判断别人可以使用哪些变量,仅仅是因为另一个环境使用了该变量名?
但是,诀窍在于,如果我们假设您的脚本是在全局范围内加载的(通过脚本标记加载该脚本的情况就是这样),则不能在外部闭包中保留变量,因为浏览器不允许这样做。现在记住在节点中,this
对象是一个空对象,但是module
变量仍然可用。那是因为它是在外部闭包中声明的。因此,我们可以通过添加额外的检查来修复下划线的检查:
this.module !== module
这样,如果有人module
在浏览器的全局范围内声明,它将被放置在this
对象中,这将导致测试失败,因为this.module
,它将与模块是同一对象。在节点上,this.module
不存在,并且module
存在于外部闭包中,因此测试将成功,因为它们不等效。
因此,最终测试是:
typeof module !== 'undefined' && this.module !== module
注意:虽然这现在允许module
在全局范围内自由使用变量,但仍然可以通过在浏览器中创建一个新的闭包并module
在其中声明,然后在该闭包中加载脚本来绕过该变量。届时,用户将完全复制节点环境,并希望知道他们在做什么,并正在尝试满足节点样式要求。如果在脚本标签中调用了代码,则对于任何新的外部闭包而言仍然是安全的。
Cannot read property 'module' of undefined
之所以得到,是因为在摩卡测试中这是未定义的,例如
以下内容在浏览器中有效,除非有意,故意破坏:
if(typeof process === 'object' && process + '' === '[object process]'){
// is node
}
else{
// not node
}
am
process+''
代替使用process.toString()
?
Object.prototype.toString.call(process)
var process = null;
将导致第二种情况失败。在这两个JavaScript和Java,表达'' + x
产生相同的x.toString()
时,除了x
是讨厌的,前者产生"null"
或"undefined"
其中后者将抛出一个错误。
这也是一种很酷的方法:
const isBrowser = this.window === this;
之所以可行,是因为在浏览器中,全局“ this”变量具有一个称为“ window”的自引用。该自引用在Node中不存在。
要破坏上面建议的浏览器,您必须执行以下操作
this.window = this;
在执行检查之前。
const isBrowser = this.window !== undefined
?从理论上讲,我可以在节点this.window = this
上欺骗解决方案。
另一个环境检测:
(含义:这里的大多数答案都可以。)
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 env中。
我们是否关心我们的环境是否被伪造?当一些愚蠢的开发人员global
在全局范围内声明一个全局变量时,就会发生这种情况。或某些邪恶的开发人员以某种方式将代码注入到我们的环境中。
我们可能会在捕获到此代码后阻止其代码执行,但应用程序的许多其他依赖项可能会陷入该代码中。因此最终代码将中断。如果您的代码足够好,那么您就不必关心别人可能犯的每一个愚蠢的错误。
如果定位两个环境:浏览器和节点;
"use strict"
; 并只需检查window
或global
; 并在文档中明确指出您的代码仅支持这些环境。而已!
var isBrowser = typeof window !== 'undefined'
&& ({}).toString.call(window) === '[object Window]';
var isNode = typeof global !== "undefined"
&& ({}).toString.call(global) === '[object global]';
如果您的用例可行;而不是环境检测;在try / catch块中进行同步特征检测。(这将需要几毫秒的时间来执行)。
例如
function isPromiseSupported() {
var supported = false;
try {
var p = new Promise(function (res, rej) {});
supported = true;
} catch (e) {}
return supported;
}
实际上,大多数提议的解决方案都是伪造的。一种可靠的方法是使用来检查Class
全局对象的内部属性Object.prototype.toString
。内部类不能在JavaScript中伪造:
var isNode =
typeof global !== "undefined" &&
{}.toString.call(global) == '[object global]';
Object.prototype.toString
,这实际上是一种不好的做法。
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
({}.toString.call(window))
等于"[object global]"
。
window.toString()
会产生"[object Window]"
怎么样使用过程中的对象和检查execPath的node
?
process.execPath
这是启动进程的可执行文件的绝对路径名。
例:
/ usr / local / bin /节点
window.process = {execPath: "/usr/local/bin/node"};
呢
脚本如何判断是否已将其作为commonjs模块使用?
相关:要检查它是否需要作为模块还是直接在节点中运行,可以检查require.main !== module
。
http://nodejs.org/docs/latest/api/modules.html#accessing_the_main_module
这是我对以上内容的修改:
(function(publish) {
"use strict";
function House(no) {
this.no = no;
};
House.prototype.toString = function() {
return "House #"+this.no;
};
publish(House);
})((typeof module == 'undefined' || (typeof window != 'undefined' && this == window))
? function(a) {this["House"] = a;}
: function(a) {module.exports = a;});
要使用它,您可以将第二行的“ House”修改为您希望模块名称在浏览器中显示的名称,然后发布您希望模块值成为的名称(通常是构造函数或对象文字) )。
在浏览器中,全局对象是window,它具有对自身的引用(有一个window.window,它是== window)。在我看来,除非您在浏览器中或希望您相信自己在浏览器中的环境中,否则这种情况不太可能发生。在所有其他情况下,如果声明了全局“模块”变量,它将使用该变量,否则将使用全局对象。
我正在process
像这样检查node.js
if (typeof(process) !== 'undefined' && process.version === 'v0.9.9') {
console.log('You are running Node.js');
} else {
// check for browser
}
要么
if (typeof(process) !== 'undefined' && process.title === 'node') {
console.log('You are running Node.js');
} else {
// check for browser
}
记录在这里
process.title
可以更改
在撰写本文时,此答案更多地是“即将推出”选项,因为它利用了JavaScript的新功能。
const runtime = globalThis.process?.release?.name || 'not node'
console.log(runtime)
该runtime
值将为node
或not node
。
如前所述,这依赖于一些新的JavaScript功能。globalThis
是ECMAScript 2020规范中的一项最终功能。Chrome 80随附的V8引擎支持可选的链接/无效合并(的?
一部分globalThis.process?.release?.name
)。自2020年4月4日起,此代码将在浏览器中运行,但在Node中将不起作用,因为Node 13分支使用V8 7.9.xxx。我相信Node 14(由于将于4/21/2020发行)应该使用V8 8.x +。
这种方法带有一定数量的电流限制。然而; 浏览器/节点的发布速度,最终将是可靠的一线。
这是一种确保服务器端和客户端javascript之间兼容性的非常安全和直接的方法,该方法也可以与browserify,RequireJS或CommonJS包含的客户端一起使用:
(function(){
// `this` now refers to `global` if we're in NodeJS
// or `window` if we're in the browser.
}).call(function(){
return (typeof module !== "undefined" &&
module.exports &&
typeof window === 'undefined') ?
global : window;
}())
编辑:关于您更新的问题:“脚本如何告诉它是否已作为commonjs模块所需?” 我不认为可以。您可以检查是否exports
是一个对象(if (typeof exports === "object")
),因为规范要求将其提供给模块,但是所有告诉您的是... exports
是一个对象。:-)
原始答案:
我确定可以检查一些特定于NodeJS的符号( 不是,您必须使用它EventEmitter
也许require
来获取事件模块;请参阅下文),但是正如David所说,最好还是检测一下该功能(而是而不是环境)。
更新:也许类似:
if (typeof require === "function"
&& typeof Buffer === "function"
&& typeof Buffer.byteLength === "function"
&& typeof Buffer.prototype !== "undefined"
&& typeof Buffer.prototype.write === "function") {
但这只是告诉您,您所处的环境require
非常类似于NodeJS Buffer
。:-)
window
在NodeJS应用程序中定义一个变量来轻松地取消检查。:-)
window
在一个模块的NodeJS,这样他们就可以包括依靠代码window
是全局对象,并没有要修改的代码。我不会这样做,你不会,但是我敢打赌有人这样做。:-)或者它们只是用来window
完全意思其他的意思。
typeof process !== "undefined" && process.title === "node"
从调试包的来源:
const isBrowser = typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs
https://github.com/visionmedia/debug/blob/master/src/index.js#L6
获取node.js的源代码并对其进行更改,以定义类似的变量runningOnNodeJS
。在代码中检查该变量。
如果您没有自己的私有版本的node.js,请在项目中打开功能请求。要求他们定义一个变量,该变量为您提供正在运行的node.js的版本。然后检查该变量。
window
全局脚本,猜测我要对该脚本提出功能请求。
var
语句,人们只是将其泄漏到全局名称空间中,所以他们当时并没有得到独立模块的概念