Как проверить, работает ли скрипт под Node.js?

159

У меня есть скрипт, который мне нужен от скрипта Node.js, который я хочу сохранить независимым от движка JavaScript.

Например, я хочу сделать exports.x = y;только если он работает под Node.js. Как я могу выполнить этот тест?


Когда я писал этот вопрос, я не знал, что функция модулей Node.js основана на CommonJS .

Для конкретного примера, который я привел, более точный вопрос был бы:

Как скрипт может определить, был ли он необходим в качестве модуля CommonJS?

theosp
источник
3
Я понятия не имею, почему вы пытаетесь это сделать, но, как правило, вы должны использовать обнаружение функций, а не обнаружение движка. quirksmode.org/js/support.html
Квентин,
4
На самом деле это запрос о том, как реализовать обнаружение функций, но этот вопрос плохо описывает себя.
монокром
опубликовал библиотеку для собственного использования, помогите, это поможет npmjs.com/package/detect-is-node
abhirathore2006
Одной из проблем с вопросом и большинством ответов является предположение, что существует только две возможности: Browser или Node.js. Существует вероятность, что это не браузер и не Node.js, как в случае с Oracle Java Nashorn. Если JDK установлен, команда jjs позволяет запускать сценарии. Но есть много различий между Nashorn и Node.js, поэтому вы не можете делать никаких предположений. И кто знает, какие варианты может предложить будущее? Обнаружение функции необходимо.

Ответы:

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

Пример здесь сохраняет шаблон модуля.

Росс
источник
45
Это обнаруживает поддержку CommonJS, которую могут поддерживать браузеры.
mikemaccana
7
Здесь есть проблема, и наильщик "прибил ее". Я пытаюсь использовать CommonJS в браузере, и загрузчик модулей, который я использую, определяет module.exports, поэтому это решение неверно скажет мне, что я нахожусь в узле.
Марк Мелвилл
1
@MarkMelville, возможно, это именно то, о чем спрашивает ОП, поэтому не проблема .
Росс
13
Плохая формулировка с моей стороны. Я имею в виду, что есть проблема с этим решением. ОП, возможно, принял это, но я нет.
Марк Мелвилл
7
Это, безусловно, НЕ лучший ответ.
user3751385
107

Что ж, нет надежного способа обнаружить запуск в Node.js, поскольку каждый веб-сайт может легко объявить одни и те же переменные, однако, поскольку windowв Node.js по умолчанию нет объекта, вы можете пойти другим путем и проверить, выполняете ли вы внутри Browser.

Вот что я использую для библиотек, которые должны работать как в браузере, так и под 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 :)
Эрик Брехемье
1
@Eric Я очень сомневаюсь, что это будет в глобальной области видимости, поэтому, если вы не импортируете что-либо, как windowв первой строке вашего модуля, у вас не должно возникнуть никаких проблем. Можно также запустить анонимную функцию и проверить [[Class]]из thisвнутри него (работает только в нестрогой режиме) в разделе «Класс» под: bonsaiden.github.com/JavaScript-Garden/#typeof
Иво Ветцель
1
Моя проблема немного отличается от OP: мне не нужен скрипт, он загружается JSDOM с эмулируемым окном в качестве глобального контекста ... Он все еще выполняется node.js + V8, просто в другом контексте, чем обычные модули.
Эрик Брехемье
1
Возможно ... Я пошел другим путем: 1) обнаружение поддержки onhashchange ("onhashchange" в окне), чтобы избежать создания бесконечного цикла 2) имитация поддержки путем установки свойства onhashchange в эмулируемом окне в сценарии main node.js.
Эрик Брехемье
1
typeof self === 'object'может быть безопаснее, так как typeof window === 'undefined'не работает в области веб-работников.
Льюис
45

В настоящее время я наткнулся на неправильное обнаружение узла, который не знает об окружении узла в Electron из-за вводящего в заблуждение обнаружения признаков. Следующие решения явно определяют среду процесса.


Определить только Node.js

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

Это обнаружит, если вы работаете в Node-процессе, так как process.releaseсодержит «метаданные, связанные с текущей версией [Node-]».

После появления io.js значение process.release.nameможет также стать io.js(см. Process-doc ). Чтобы правильно определить Node-ready среду, я думаю, вам следует проверить следующее:

Определить узел (> = 3.0.0) или io.js

(typeof process !== 'undefined') &&
(process.release.name.search(/node|io.js/) !== -1)

Это утверждение было проверено с Узлом 5.5.0, Электроном 0.36.9 (с Узлом 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: я публикую этот ответ здесь, так как вопрос привел меня сюда, хотя ОП искал ответ на другой вопрос.

Флориан Брейш
источник
2
Это, похоже, самый надежный подход, спасибо. Хотя работает только для версии> = 3.0.0.
filip
@daluege - спасибо за вдохновение. К сожалению, я не нашел доказательства ниже 0,10.
Флориан Брейш
3
Я обнаружил, что использую реагирующий веб-пакет, processи process.versionсуществует в пакете, поэтому я добавил дополнительную проверку для того, process.versionгде process.release.nodeне определено на стороне клиента, но имеет версию узла в качестве значения на стороне сервера
Аарон
@ Аарон: спасибо за эту подсказку. Я не смог найти какое-либо определение process.versionпеременной (в реагировать, веб-пакет или реагировать-веб-пакет). Я был бы признателен за любую подсказку, где определяется переменная версии, чтобы добавить ее в ответ. В зависимости от ограничений release.node для узла> = 3.xx
Флориан Брейш
2
Один вкладыш и безопаснее:function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
Brillout
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
}

Bam.

user3751385
источник
4
var process = {toString: function () {return '[object process]'; }};
Ник Desaulniers
1
Есть ли какая-то причина, почему вы используете process+''вместо process.toString()?
вредный
3
Почти. Используйте это вместо:Object.prototype.toString.call(process)
Соспедра
2
Это лучший ответ на этот вопрос.
loretoparisi
3
@harmic: var process = null;приведет к сбою во втором случае. И в Javascript, и в Java выражение '' + xвыдает то же самое, что и x.toString()за исключением случаев, когда xэто плохо, первое выдает "null"или "undefined"где последнее выдает ошибку.
joeytwiddle
17

Вот довольно крутой способ сделать это:

const isBrowser = this.window === this;

Это работает, потому что в браузерах глобальная переменная this имеет собственную ссылку под названием window. Эта собственная ссылка не существует в узле.

  • В браузере «this» является ссылкой на глобальный объект, называемый «окном».
  • В узле «this» является ссылкой на объект module.exports.
    • «this» не является ссылкой на глобальный объект Node, называемый «global».
    • «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объекту в браузере. Так что это будет означать, что вы находитесь в Ende Node внутри браузера.

Жизнь коротка!

Неужели мы заботимся о том, что наша среда подделана? Это происходит, когда какой-то глупый разработчик объявляет глобальную переменную, называемую globalв глобальной области видимости. Или какой-то злой разработчик как-то внедряет код в нашу среду.

Мы можем препятствовать выполнению нашего кода при обнаружении этого, но множество других зависимостей нашего приложения могут попасть в это. Так что в конечном итоге код сломается. Если ваш код достаточно хорош, вы не должны заботиться о каждой глупой ошибке, которую могли совершить другие.

Ну и что?

При ориентации на 2 среды: браузер и узел;
"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;
}
Онур Йылдырым
источник
9

Большинство предложенных решений могут быть подделаны. Надежный способ - проверить внутреннее Classсвойство глобального объекта с помощью Object.prototype.toString. Внутренний класс не может быть подделан в JavaScript:

var isNode = 
    typeof global !== "undefined" && 
    {}.toString.call(global) == '[object global]';
Фабиан Якобс
источник
2
Это вернется под browserify.
альт
1
Вы проверяли это? Я не вижу, как browserify может изменить внутренний класс объекта. Это потребует изменения кода в виртуальной машине JavaScript или перезаписи, Object.prototype.toStringчто является очень плохой практикой.
Фабиан Якобс
Я проверял это. Вот что делает browserify: var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
Вануан
Видите ли, в Chrome, ({}.toString.call(window))это равный "[object global]".
Вануан
2
Это странно, потому что window.toString()производит"[object Window]"
Вануан
5

Как насчет использования объекта процесса и проверки execPath для node?

process.execPath

Это абсолютный путь к исполняемому файлу, который запустил процесс.

Пример:

/ USR / местные / бен / узел

Кевин Хакансон
источник
2
Как насчет window.process = {execPath: "/usr/local/bin/node"};?
Константин Ван
4

Как скрипт может определить, был ли он необходим в качестве модуля commonjs?

Связано: чтобы проверить, был ли он необходим, поскольку модуль запускался непосредственно в узле, вы можете проверить require.main !== module. http://nodejs.org/docs/latest/api/modules.html#accessing_the_main_module

Пит
источник
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;});

Чтобы использовать его, вы изменяете «Дом» во второй последней строке, чтобы он был таким, каким вы хотите, чтобы имя модуля отображалось в браузере, и публикуете все, что хотите, чтобы значение модуля было (обычно конструктор или литерал объекта). ).

В браузерах глобальным объектом является window, и он имеет ссылку на себя (есть window.window, который == window). Мне кажется, что это вряд ли произойдет, если вы не находитесь в браузере или в среде, которая хочет, чтобы вы верили, что вы в браузере. Во всех других случаях, если объявлена ​​глобальная переменная 'module', она использует ее, в противном случае она использует глобальный объект.

kybernetikos
источник
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можно изменить
Бен Баркай,
Затем проверьте название, на которое вы его изменили. Или используйте process.version
Chris
Если вы пишете для библиотеки (как вы должны), вы не сможете ожидать, какой должен быть заголовок
Бен Баркай,
3

На момент написания этой статьи этот ответ представлял собой скорее вариант «Скоро появится», поскольку он использует очень новые функции JavaScript.

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

runtimeЗначение будет либо nodeили not node.

Как уже упоминалось, это зависит от нескольких новых функций JavaScript. globalThisявляется финальной функцией в спецификации ECMAScript 2020 Необязательное объединение в цепочку / нулевое объединение ( ?часть globalThis.process?.release?.name) поддерживается в движке V8, который поставляется с Chrome 80. Начиная с 08.04.2020 этот код будет работать в браузере, но не будет работать в Node, поскольку ветвь Node 13 использует V8 7.9.xxx. Я считаю, что Node 14 (который должен быть выпущен 21.04.2020) должен использовать V8 8.x +.

Этот подход идет со здоровой дозой текущих ограничений. Тем не мение; скорость, с которой выпускаются браузеры / Node, в конечном итоге станет надежной.

Corey
источник
1
Это должен быть принятый ответ! и все должны использовать узел 14, кстати
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для получения модуля событий; см. Ниже ), который вы могли бы проверить, но, как сказал Дэвид, в идеале вам лучше обнаружить функцию (скорее чем окружающая среда), если это имеет какой-то смысл.

Обновление : возможно что-то вроде:

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

Но это просто говорит о том, что вы находитесь в среде с requireчем-то очень похожим на NodeJS Buffer. :-)

TJ Crowder
источник
Я все еще могу сломать это, настроив все эти вещи на веб-сайте ... это просто излишне;) Проверять наличие в браузере проще, поскольку среда Node чище.
Иво Ветцель
1
@ Иво: Да, смотрите мое последнее предложение. Я так же легко могу сломать вашу проверку, определив windowпеременную в приложении NodeJS. :-)
TJ Crowder
1
@Ivo: Я бы не быть все удивлено , если кто - то определенное windowв модуле NodeJS, таким образом , они могут включать в себя код , который полагался на windowвремя глобального объекта и не хочет , чтобы изменить этот код. Я бы не стал этого делать, вы бы не стали, но держу пари, что кто-то сделал :-) Или они просто имели windowв виду что-то совсем другое.
TJ Crowder
1
@Ivo: yuiblog.com/blog/2010/04/09/… это одна из причин, по которой объект окна может быть определен в node.js
slebetman
1
@TJCrowdertypeof process !== "undefined" && process.title === "node"
Райнос
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. И в настоящее время есть код, который делает это.
Slebetman
@ Slebetman Нет, это не сломает JSDOM. Я говорю о глобальном , как в глобальном выражении no var , в приведенном здесь примере кода используется varвыражение, люди, которые просто просачивают его в глобальное пространство имен, ну, тогда они не понимают концепцию автономных модулей
Иво Ветцель,
@ Иво, что-то вроде насилия, это все равно, что сказать, что мы должны есть способность есть торты, потому что люди толстеют, переедая их. Вы должны загромождать глобальное пространство имен, чтобы создать библиотеку, которая будет работать между модулями. Или вы можете обернуть все это в один модуль, но тогда какой в ​​этом смысл?
Бен Баркай
-1

Очень старый пост, но я решил его, обернув операторы require в try - catch

try {
     var fs = require('fs')
} catch(e) {
     alert('you are not in node !!!')
}
Стеф де Врис
источник
2
Это неправда, вы можете использовать browserify, чтобы использовать «nodeish» вызовы require ()
толстый