Як перевірити, чи працює сценарій під Node.js?


159

У мене є сценарій, який я вимагаю від сценарію Node.js, від якого я хочу залишати механізм JavaScript незалежним.

Наприклад, я хочу це зробити, exports.x = y;лише якщо він працює під Node.js. Як я можу виконати цей тест?


Опублікувавши це питання, я не знав, що функція модулів Node.js заснована на CommonJS .

У конкретному прикладі, який я наводив, точнішим питанням було б:

Як сценарій може визначити, чи потрібен він як модуль CommonJS?


3
Я не знаю, чому ви намагаєтеся це зробити, але як правило, ви повинні використовувати функцію виявлення функцій, а не виявлення двигуна. quirksmode.org/js/support.html
Квентін

4
Це насправді запит про те, як реалізувати функцію виявлення функцій, але питання погано описує себе.
монокром

опублікував бібліотеку для власного користування, допоможіть це допоможе npmjs.com/package/detect-is-node
abhirathore2006


Однією із проблем, що стосуються питання, та більшості відповідей, є припущення, що існує лише дві можливості: браузер або 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 немає об'єкта за замовчуванням, ви можете піти навпаки і перевірити, чи працює ви всередині Веб-переглядач.

Це те, що я використовую для libs, які повинні працювати як у браузері, так і під Node.js:

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

} else {
    window.foo = {};
}

Він може все-таки вибухнути у випадку, windowвизначеному в Node.js, але немає вагомих причин для когось це робити, оскільки вам явно потрібно буде залишити varабо встановити властивість на globalоб'єкт.

EDIT

Визначити, чи потрібен ваш сценарій як модуль 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
Моя проблема дещо відрізняється від оперативної програми: мені не потрібен сценарій, він завантажується JSDOM з емуляційним вікном як глобальний контекст ... Він все ще працює node.js + V8, просто в іншому контексті, ніж звичайні модулі.
Eric Bréchemier

1
Можливо ... Я пішов іншим напрямком: 1) виявити підтримку onhashchange ("onhashchange" у вікні), щоб уникнути створення нескінченного циклу. 2) імітувати підтримку, встановивши властивість onhashchange на емульоване вікно в головному скрипті node.js.
Ерік Бреч'єр

1
typeof self === 'object'може бути безпечнішим, оскільки typeof window === 'undefined'не працює у сфері роботи веб-працівників.
Льюїс

45

Наразі я натрапив на неправильне виявлення Node, який не знає про середовище Node в Electron через виявлення оманливої ​​функції. Наступні рішення чітко визначають середовище процесу.


Ідентифікуйте лише Node.js

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

Це виявить, якщо ви працюєте в Node-процесі, оскільки process.releaseмістить "метадані, пов'язані з поточним випуском [Node-]".

Після нересту io.js значення process.release.nameтакож може стати io.js(див. Процес-doc ). Для належного виявлення середовища, готового до вузла, я думаю, вам слід перевірити наступне:

Визначте Node (> = 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.

Визначте Node (> = 0.10.0) або io.js

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

Коментар @ daluege надихнув мене думати про більш загальне доказ. Це має працювати з Node.js> = 0,10 . Я не знайшов унікального ідентифікатора для попередніх версій.


Пс: Я публікую цю відповідь тут, оскільки питання веде мене сюди, хоча ОП шукала відповідь на інше питання.


2
Це, здається, є найбільш надійним підходом, дякую. Хоча працює лише для версії> = 3.0.0.
Філіп

@daluege - дякую за натхнення. Я, на жаль, не знайшов доказів нижче 0,10.
Флоріан Брейш

3
Я виявив, що використовує webpack react, processі він process.versionіснує в комплекті, тому я додав додатковий чек про те, process.versionде process.release.nodeне визначено на стороні клієнта, але має версію вузла як значення на стороні сервера
Аарон,

@Aaron: дякую за цей натяк. Я не зміг знайти жодного визначення process.versionзмінної (в react, webpack або react-webpack). Буду вдячний за будь-яку підказку, де визначена змінна версія, щоб додати її до відповіді. Залежно від обмежень
release.node

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
}

Бам.


4
var process = {toString: function () {return '[об'єктний процес]'; }};
Нік Десольньє

1
Чи є якась причина, чому ви використовуєте process+''замість цього process.toString()?
шкідливий

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;

Це працює, тому що у веб-переглядачах глобальна змінна "ця" має власне посилання під назвою "вікно". Ця самопосилання не існує в Вузлі.

  • У браузері "це" - це посилання на глобальний об'єкт, який називається "вікно".
  • У Вузлі "це" - це посилання на об'єкт module.exports.
    • "цього" немає є посиланням на глобальний об'єкт "Вузол", який називається "глобальним".
    • "це" не посилається на простір декларації змінної модуля.

Щоб порушити вищевказану перевірку браузера, вам доведеться зробити щось на кшталт наступного

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 у глобальному масштабі. Або якийсь злий диявол якось вводить код у наше оточення.

Ми можемо не допустити виконання нашого коду, коли ми це вловимо, але в цьому може потрапити безліч інших залежностей нашого додатка. Так з часом код зламається. Якщо ваш код досить хороший, ви не повинні дбати про кожну нерозумну помилку, яку могли зробити інші.

І що?

Якщо націлено на 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]';

Якщо це можливо для вашого випадку використання; замість виявлення навколишнього середовища; робити синхронне виявлення функцій у блоці спробу / лову. (на виконання знадобиться кілька мілісекунд).

напр

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
Це повернеться під час перегляду.
alt

1
Ви тестували це? Я не бачу, як веб-переглядач може змінити внутрішній клас об'єкта. Для цього знадобиться змінити код у віртуальній машині JavaScript або перезаписати, Object.prototype.toStringщо є дійсно поганою практикою.
Фабіан Якобс

Я тестував це. Ось що робить перегляд веб-переглядачів: 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 / local / bin / node


2
Про що window.process = {execPath: "/usr/local/bin/node"};?
Константин Ван


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можна змінити
Бен Баркай

Потім перевірте назву, на яку ви її змінили. Або скористайтеся process.version
Кріс

Якщо ви пишете для бібліотеки (як і слід), ви не зможете очікувати, якою має бути назва
Бен Баркай

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. Станом на 4/8/2020 цей код буде працювати в браузері, але не працюватиме у Вузлі, оскільки використовується відділення Node 13 V8 7.9.xxx. Я вважаю, що Вузол 14 (належить до виходу 21.04.2020) повинен використовувати V8 8.x +.

Цей підхід має здорову дозу обмежень струму. Однак; темп, з яким випускаються браузери / Вузол, з часом стане надійним однолінійним.


1
Це має бути прийнята відповідь! і кожен повинен використовувати вузол 14 btw
Сцет

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 та стороною клієнта, який також буде працювати з клієнтом на веб-переглядачі, в вимогуJJJS або 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. :-)


Я все одно можу це зламати, встановивши всі ці речі на веб-сайті ... це просто зайве;) Перевірка на наявність у веб-переглядачі простіше, оскільки середовище Node є більш чистим.
Іво Ветцель

1
@Ivo: Так, дивіться моє останнє речення. Я можу так само легко порушити ваш чек, визначивши 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. Я кажу про глобальний , як у жодному вартісному заяві var , у прикладі цього коду використовується varзаява, люди, які просто просочують його у глобальний простір імен, добре, що тоді вони не отримують поняття автономних модулів
Ivo Wetzel

@Ivo це жорстоке, це як би сказати, що ми повинні використовувати здатність їсти торти, тому що люди наїдаються жиру, переїдаючи їх. Ви повинні захаращувати глобальний простір імен, щоб створити бібліотеку, яка буде працювати міжмодульним. Або ти зможеш все це згорнути в один модуль, але тоді в чому сенс?
Бен Баркай

-1

Дуже стара публікація, але я просто вирішив її, загорнувши заявки на вимогу у спробу

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

2
Це неправда, ви можете скористатися переглядом, щоб використовувати "nodeish" call () дзвінки
жир
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.