173

我有一个需要来自 Node.js 脚本的脚本,我想保持 JavaScript 引擎独立。

例如,我只想在exports.x = y;它在 Node.js 下运行时才这样做。我该如何进行这个测试?


发布此问题时,我不知道 Node.js 模块功能基于CommonJS

对于我给出的具体示例,更准确的问题是:

脚本如何判断它是否需要作为 CommonJS 模块?

4

22 回答 22

116

好吧,没有可靠的方法来检测 Node.js 中的运行,因为每个网站都可以轻松声明相同的变量,但是,由于window默认情况下 Node.js 中没有对象,你可以反过来检查你是否在一个浏览器。

这是我用于应该在浏览器和 Node.js 下工作的库:

if (typeof window === 'undefined') {
    exports.foo = {};

} else {
    window.foo = {};
}

如果在 Node.js 中定义,它可能仍然会爆炸,window但没有充分的理由让某人这样做,因为您将明确需要省略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在那里声明。

于 2010-11-19T11:51:17.077 回答
83

通过寻找 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._ = _;
    }
})();

此处的示例保留了模块模式。

于 2011-03-04T17:34:12.817 回答
51

我目前偶然发现了一个错误的 Node 检测,由于误导性的特征检测,它知道Electron中的 Node 环境。以下解决方案明确识别过程环境。


仅识别 Node.js

(typeof process !== 'undefined') && (process.release.name === 'node')

这将发现您是否在节点进程中运行,因为process.release包含“与当前 [Node-] 版本相关的元数据”。

io.js生成之后,的值process.release.name也可能变为io.js(参见process-doc)。为了正确检测节点就绪环境,我想您应该检查如下:

识别节点 (>= 3.0.0) 或 io.js

(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 进行了测试。

识别节点 (>= 0.10.0) 或 io.js

(typeof process !== 'undefined') &&
(typeof process.versions.node !== 'undefined')

@daluege 的评论启发我思考一个更普遍的证明。这应该适用于 Node.js >= 0.10。我没有找到以前版本的唯一标识符。


Ps:我在这里发布了这个答案,因为这个问题把我带到了这里,尽管 OP 正在寻找一个不同问题的答案。

于 2016-03-05T10:46:24.933 回答
26

试图弄清楚你的代码在什么环境中运行的问题是任何对象都可以被修改和声明,这使得几乎不可能弄清楚哪些对象是环境的原生对象,哪些对象已经被程序修改了。

但是,我们可以使用一些技巧来确定您所处的环境。

让我们从下划线库中使用的普遍接受的解决方案开始:

typeof module !== 'undefined' && module.exports

这种技术实际上对于服务器端来说非常好,因为当require调用该函数时,它会将this对象重置为空对象,并module再次为您重新定义,这意味着您不必担心任何外部篡改。只要你的代码是用 加载的require,你就是安全的。

但是,这在浏览器上会分崩离析,因为任何人都可以轻松定义module以使其看起来像是您正在寻找的对象。一方面,这可能是您想要的行为,但它也规定了库用户可以在全局范围内使用哪些变量。也许有人想使用一个变量,其中包含它的名称moduleexports供其他用途。这不太可能,但是我们凭什么来判断其他人可以使用哪些变量,仅仅因为另一个环境使用该变量名?

然而,诀窍是,如果我们假设您的脚本正在全局范围内加载(如果它是通过脚本标签加载的),则不能在外部闭包中保留变量,因为浏览器不允许这样做. 现在记住在节点中,this对象是一个空对象,然而,module变量仍然可用。那是因为它是在外部闭包中声明的。所以我们可以通过添加一个额外的检查来修复下划线的检查:

this.module !== module

这样,如果有人module在浏览器的全局范围内声明,它会被放在this对象中,这将导致测试失败,因为this.module, 将与模块是同一个对象。在节点上,this.module不存在,并且module存在于外部闭包中,因此测试将成功,因为它们不等价。

因此,最终的测试是:

typeof module !== 'undefined' && this.module !== module

注意:虽然这现在允许module在全局范围内自由使用变量,但仍然可以通过创建一个新的闭包并module在其中声明,然后在该闭包中加载脚本来绕过浏览器。那时,用户正在完全复制节点环境,并希望知道他们在做什么,并试图满足节点风格的要求。如果代码在脚本标签中被调用,它仍然不会受到任何新的外部闭包的影响。

于 2012-08-11T23:06:41.177 回答
22

除非故意,明确破坏,否则以下内容在浏览器中有效:

if(typeof process === 'object' && process + '' === '[object process]'){
    // is node
}
else{
    // not node
}

巴姆。

于 2014-06-18T07:34:14.083 回答
18

这也是一个非常酷的方法:

const isBrowser = this.window === this;

这是因为在浏览器中,全局“this”变量有一个名为“window”的自引用。Node 中不存在此自引用。

  • 在浏览器中,“this”是对全局对象的引用,称为“window”。
  • 在节点中,'this' 是对 module.exports 对象的引用。
    • 'this'不是对 Node 全局对象的引用,称为 'global'。
    • 'this'不是对模块变量声明空间的引用。

要打破上述建议的浏览器检查,您必须执行以下操作

this.window = this;

在执行检查之前。

于 2017-01-25T23:21:01.600 回答
14

另一个环境检测

(意思是:这里的大多数答案都很好。)

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 ?..
}

有点偏执吧?您可以通过检查更多globals来使其更详细。

但是不要!

无论如何,以上所有这些都可以伪造/模拟。

例如伪造global对象:

global = {
    toString: function () {
        return '[object global]';
    },
    GLOBAL: global,
    setImmediate: function (a, b, c, d) {}
 };
 setImmediate = function (a, b, c, d) {};
 ...

这不会附加到节点的原始全局对象,但会附加到window浏览器中的对象。因此,这意味着您在浏览器中的 Node 环境中。

生命短暂!

我们是否关心我们的环境是否被伪造?当一些愚蠢的开发人员声明一个global在全局范围内调用的全局变量时,就会发生这种情况。或者一些邪恶的开发者以某种方式在我们的环境中注入代码。

当我们捕捉到这一点时,我们可能会阻止我们的代码执行,但我们应用程序的许多其他依赖项可能会陷入其中。所以最终代码会中断。如果你的代码足够好,你就不应该关心别人可能犯的每一个愚蠢的错误。

所以呢?

如果针对 2 个环境:浏览器和节点;
"use strict"; 或者简单地检查windowor 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;
}
于 2016-08-07T15:38:54.517 回答
9

大多数提出的解决方案实际上都可以伪造。一种可靠的方法是Class使用Object.prototype.toString. 在 JavaScript 中不能伪造内部类:

var isNode = 
    typeof global !== "undefined" && 
    {}.toString.call(global) == '[object global]';
于 2013-08-05T11:53:42.190 回答
6

在撰写本文时,这个答案更像是一个“即将推出”的选项,因为它利用了 JavaScript 的非常新的特性。

const runtime = globalThis.process?.release?.name || 'not node'
console.log(runtime)

runtime值将是nodenot node

如前所述,这依赖于一些新的 JavaScript 特性。globalThis是 ECMAScript 2020 规范中的最终功能。Chrome 80 附带的 V8 引擎支持可选的链式/无效合并(的?部分)。截至 2020 年 4 月 8 日,此代码将在浏览器中运行,但在 Node 中无法运行,因为 Node 13 分支使用globalThis.process?.release?.nameV8 7.9.xxx。我相信 Node 14(将于 2020 年 4 月 21 日发布)应该使用 V8 8.x+。

这种方法带有健康剂量的当前限制。然而; 浏览器/节点发布的速度,最终将成为可靠的单线。

于 2020-04-08T22:22:58.787 回答
4

这是我对上述内容的变体:

(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)。在我看来,除非您在浏览器中或在希望您相信自己在浏览器中的环境中,否则这不太可能发生。在所有其他情况下,如果声明了全局“模块”变量,则使用该变量,否则使用全局对象。

于 2012-04-13T13:27:45.837 回答
4

脚本如何判断它是否需要作为 commonjs 模块?

相关:要检查它是否需要作为模块与直接在节点中运行,您可以检查require.main !== module. http://nodejs.org/docs/latest/api/modules.html#accessing_the_main_module

于 2011-12-05T17:04:41.437 回答
4

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
}

记录在这里

于 2013-04-04T14:14:26.033 回答
4

使用进程对象并检查execPath怎么node样?

process.execPath

这是启动进程的可执行文件的绝对路径名。

例子:

/usr/local/bin/节点

于 2010-11-21T14:14:02.660 回答
2

Node.js 有process对象,所以只要您没有任何其他创建的脚本,process您就可以使用它来确定代码是否在 Node 上运行。

var isOnNodeJs = false;
if(typeof process != "undefined") {
  isOnNodeJs = true;
}

if(isOnNodeJs){
  console.log("you are running under node.js");
}
else {
  console.log("you are NOT running under node.js");
}
于 2015-01-13T20:33:33.660 回答
2

这是确保服务器端和客户端 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;
}())
于 2014-07-31T16:02:58.540 回答
1

编辑:关于您更新的问题:“脚本如何判断它是否需要作为 commonjs 模块?” 我不认为它可以。您可以检查是否exports是对象 ( if (typeof exports === "object")),因为规范要求将其提供给模块,但告诉您的只是 ...exports是一个对象。:-)


原答案:

我确定您可以检查一些特定于 NodeJS 的符号(EventEmitter也许 不,您必须使用它require来获取事件模块;见下文),但正如大卫所说,理想情况下您最好检测该功能(而不是比环境)如果这样做有意义的话。

更新:也许是这样的:

if (typeof require === "function"
    && typeof Buffer === "function"
    && typeof Buffer.byteLength === "function"
    && typeof Buffer.prototype !== "undefined"
    && typeof Buffer.prototype.write === "function") {

但这只是告诉您,您处于一个require非常非常类似于 NodeJS 的环境中Buffer。:-)

于 2010-11-19T11:47:01.957 回答
1
const isNode =
  typeof process !== 'undefined' &&
  process.versions != null &&
  process.versions.node != null;
于 2019-07-30T17:56:56.600 回答
1

从调试包的来源:

const isBrowser = typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs

https://github.com/visionmedia/debug/blob/master/src/index.js#L6

于 2019-10-04T15:13:57.280 回答
0

非常旧的帖子,但我只是通过将 require 语句包装在 try-catch 中来解决它

try {
     var fs = require('fs')
} catch(e) {
     alert('you are not in node !!!')
}
于 2014-07-24T20:32:51.180 回答
0

很老的帖子,但我结合其他答案解决了:

var isNode=()=>!("undefined"!=typeof window||"object"!=typeof module||!module.exports||"object"!=typeof process||!process.moduleLoadList);
console.log(isNode()); //=> false

于 2021-03-27T05:31:53.873 回答
0

这并不能直接回答您的问题,因为您想专门检查 Node.js,但是它足够有用,可以保证说:

大多数情况下,如果您只想区分浏览器和服务器端 javascript,只需检查文档是否存在就足够了。

if (typeof document !== 'undefined') {} // do stuff

// This one is overkill, but 100% always works:
if (typeof window !== 'undefined' && window && window.window === window) {
   if (typeof window.document !== 'undefined' && document.documentElement) {

   }
}
于 2020-10-20T13:35:51.123 回答
-1

获取 node.js 的源代码并将其更改为定义一个变量,例如runningOnNodeJS. 在您的代码中检查该变量。

如果您无法拥有自己的私有版本的 node.js,请在项目中打开功能请求。要求他们定义一个变量,为您提供正在运行的 node.js 版本。然后检查该变量。

于 2010-11-19T12:35:12.907 回答