如何检查脚本是否在Node.js下运行?


159

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

例如,我只想exports.x = y;在Node.js下运行。如何执行此测试?


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

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

脚本如何判断是否已将其作为CommonJS模块使用?


3
我不知道为什么要尝试这样做,但是根据经验,应该使用功能检测而不是引擎检测。quirksmode.org/js/support.html
Quentin 2010年

4
这实际上是对如何实现特征检测的要求,但是这个问题很难自我描述。
monokrome

发布了一个供我自己使用的库,这将有助于npmjs.com/package/detect-is-node
abhirathore2006 '16


这个问题以及大多数答案中的一个问题是,假设只有两种可能性:浏览器或Node.js。可能既不是浏览器也不是Node.js,例如Oracle Java Nashorn。如果安装了JDK,则可以使用jjs命令运行脚本。但是Nashorn和Node.js之间有很多区别,因此您无法做任何假设。谁知道未来可能带来什么选择?需要功能检测。

Answers:


80

通过寻求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模式。


45
这将检测到CommonJS支持,浏览器可能支持。
mikemaccana 2012年

7
这是一个问题,钉子已“钉上钉子”。我在浏览器中尝试使用CommonJS,并且我使用的模块加载器定义了module.exports,所以此解决方案会错误地告诉我我在node中。
Mark Melville

1
@MarkMelville可以说,这正是OP的要求,因此不是问题
罗斯,

13
我的措辞不好。我的意思是这个解决方案有问题。OP可能已经接受了,但我没有接受。
马克梅尔维尔

7
这当然不是给出的最佳答案。
user3751385 2014年

107

嗯,没有可靠的方法来检测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在那里声明。


2
关于“ Node.js是一种更清洁的环境,因此不太可能在其中声明窗口。”:嗯,我只是来这里寻找一种方法来找出我的脚本是否在由node.js + JSDOM模拟的浏览器中运行还是在普通的浏览器中...原因是我有一个无限循环使用setTimeout来检查URL位置,这在浏览器中很好,但是可以使node.js脚本永远运行...所以可能有一个窗口毕竟是在node.js脚本中:)
EricBréchemier'11

1
@Eric我非常怀疑它是否会出现在全局范围内,因此除非您像window在模块的第一行中那样导入内容,否则应该不会有任何问题。您也可以运行一个匿名函数,并检查[[Class]]this里面(仅适用于非严格模式)请参阅“类”下:bonsaiden.github.com/JavaScript-Garden/#typeof
伊沃·韦策尔

1
我的问题与OP的问题略有不同:我不需要脚本,它是由JSDOM加载的,具有作为全局上下文的模拟窗口...它仍然由node.js + V8运行,只是在与通常模块不同的上下文中。
EricBréchemier'02

1
可能...我走了另一个方向:1)检测对onhashchange(窗口中的“ onhashchange”)的支持以避免创建无限循环2)通过在主node.js脚本中的仿真窗口上设置onhashchange属性来模拟支持。
EricBréchemier'11

1
typeof self === 'object'可能会更安全,因为typeof window === 'undefined'在Web worker范围上失败。
刘易斯

45

我目前偶然发现了一个错误的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开始工作。我没有找到先前版本的唯一标识符。


附:我将这个答案发布在这里,因为这个问题将我引到了这里,尽管OP正在寻找另一个问题的答案。


2
到目前为止,这似乎是最可靠的方法。虽然仅适用于版本> = 3.0.0。
flip

@daluege-感谢您的灵感。不幸的是,我没有找到低于0.10的证明。
Florian Breisch

3
我发现使用反应的WebPack,processprocess.version在包内存在,所以我增加了一个额外的检查process.version,其中process.release.node在客户端上是不明确的,但有一个节点版本服务器端的值
阿伦

@Aaron:感谢您的提示。我找不到该process.version变量的任何定义(在react,webpack或react-webpack中)。我会很高兴在定义版本变量以将其添加到答案的任何提示。取决于release.node对节点> = 3.xx的约束
Florian Breisch

2
function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
单线

25

试图弄清楚您的代码在哪个环境中运行的问题是,可以修改和声明任何对象,从而几乎不可能弄清哪些对象是环境的本机对象,以及哪些对象已被程序修改。

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

让我们从下划线库中使用的公认解决方案开始:

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在其中声明,然后在该闭包中加载脚本来绕过该变量。届时,用户将完全复制节点环境,并希望知道他们在做什么,并正在尝试满足节点样式要求。如果在脚本标签中调用了代码,则对于任何新的外部闭包而言仍然是安全的。


2
哇,谢谢您清楚地解释了单线背后的基本原理。
乔恩·库姆斯

Cannot read property 'module' of undefined之所以得到,是因为在摩卡测试中这是未定义的,例如
srghma

20

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

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

am


4
var process = {toString:function(){return'[object process]'; };
Nick Desaulniers 2014年

1
有什么原因可以process+''代替使用process.toString()
harmic

3
几乎。改用它:Object.prototype.toString.call(process)
sospedra

2
这是对这个问题的最佳答案。
loretoparisi

3
@harmic:var process = null;将导致第二种情况失败。在这两个JavaScript和Java,表达'' + x产生相同的x.toString()时,除了x是讨厌的,前者产生"null""undefined"其中后者将抛出一个错误。
joeytwiddle

17

这也是一种很酷的方法:

const isBrowser = this.window === this;

之所以可行,是因为在浏览器中,全局“ this”变量具有一个称为“ window”的自引用。该自引用在Node中不存在。

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

要破坏上面建议的浏览器,您必须执行以下操作

this.window = this;

在执行检查之前。


为什么不简单const isBrowser = this.window !== undefined?从理论上讲,我可以在节点this.window = this上欺骗解决方案。
泰勒·朗

11

另一个环境检测

(含义:这里的大多数答案都可以。)

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"; 并只需检查windowglobal; 并在文档中明确指出您的代码仅支持这些环境。而已!

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;
}

9

实际上,大多数提议的解决方案都是伪造的。一种可靠的方法是使用来检查Class全局对象的内部属性Object.prototype.toString。内部类不能在JavaScript中伪造:

var isNode = 
    typeof global !== "undefined" && 
    {}.toString.call(global) == '[object global]';

2
这将在browserify下恢复。
alt 2014年

1
你测试了吗?我看不到browserify如何更改对象的内部类。这将需要更改JavaScript VM中的代码或进行覆盖Object.prototype.toString,这实际上是一种不好的做法。
Fabian Jakobs 2014年

我测试了 这是var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
browserify的

您会发现,在Chrome中,({}.toString.call(window))等于"[object global]"
Vanuan 2014年

2
很奇怪,因为window.toString()会产生"[object Window]"
Vanuan



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


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
}

记录在这里


2
process.title可以更改
Ben Barkay 2013年

然后检查您将其更改为的标题。或使用process.version
克里斯(Chris)

如果您正在为图书馆写书(就像应该那样),那么您将无法期望标题是什么
Ben Barkay 2013年

3

在撰写本文时,此答案更多地是“即将推出”选项,因为它利用了JavaScript的新功能。

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

runtime值将为nodenot 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 +。

这种方法带有一定数量的电流限制。然而; 浏览器/节点的发布速度,最终将是可靠的一线。


1
这应该是公认的答案!每个人都应该使用节点14 btw
Sceat

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");
}

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;
}())

1

编辑:关于您更新的问题:“脚本如何告诉它是否已作为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。:-)


我仍然可以通过在网站中设置所有内容来打破这一点……这太过分了;)由于Node环境更干净,因此检查是否位于浏览器中更加容易。
伊沃·韦策尔

1
@Ivo:是的,请参阅我的最后一句话。我可以通过window在NodeJS应用程序中定义一个变量来轻松地取消检查。:-)
TJ Crowder 2010年

1
@Ivo:我不会在所有惊讶,如果有人定义window在一个模块的NodeJS,这样他们就可以包括依靠代码window是全局对象,并没有要修改的代码。不会这样做,不会,但是我敢打赌有人这样做。:-)或者它们只是用来window完全意思其他的意思。
TJ Crowder 2010年

1
@Ivo:yuiblog.com/blog/2010/04/09/…是可在node.js中定义窗口对象的原因之一
slebetman 2010年

1
@TJCrowdertypeof process !== "undefined" && process.title === "node"
雷诺斯2012年

0
const isNode =
  typeof process !== 'undefined' &&
  process.versions != null &&
  process.versions.node != null;


-1

获取node.js的源代码并对其进行更改,以定义类似的变量runningOnNodeJS。在代码中检查该变量。

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


1
再次不能解决他的问题(基本上无法解决),我可以再次在浏览器中创建这样的变量。更好的方法是阻止脚本创建window全局脚本,猜测我要对该脚本提出功能请求。
伊沃·韦策尔

@Ivo:这是一个坏主意,它会破坏使用jsdom(github.com/tmpvar/jsdom)的代码,以使用熟悉的YUI和jQuery库进行服务器端dom操作。目前在生产中有执行此操作的代码。
slebetman 2010年

@slebetman不,它不会破坏jsdom。我说的是global,就像no var语句中的global一样,那里的示例代码使用该var语句,人们只是将其泄漏到全局名称空间中,所以他们当时并没有得到独立模块的概念
Ivo Wetzel

@Ivo有点暴力,就像在说我们应该有能力吃蛋糕,因为人们胖子暴饮暴食。您必须使全局名称空间杂乱无章,以实现可在模块间工作的库。或者,您可以将它们全部包装在一个模块中,那又有什么意义呢?
本·巴凯

-1

很老的帖子,但我只是通过尝试包装require语句解决了-catch

try {
     var fs = require('fs')
} catch(e) {
     alert('you are not in node !!!')
}

2
事实并非如此,您可以使用browserify来使用“节点式” require()调用
fat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.