¿Cómo verificar si un script se ejecuta en Node.js?

159

Tengo un script que necesito de un script Node.js, que quiero mantener el motor de JavaScript independiente.

Por ejemplo, quiero hacerlo exports.x = y;solo si se ejecuta en Node.js. ¿Cómo puedo realizar esta prueba?


Al publicar esta pregunta, no sabía que la función de módulos Node.js se basa en CommonJS .

Para el ejemplo específico que di, una pregunta más precisa habría sido:

¿Cómo puede un script decir si se ha requerido como un módulo CommonJS?

theosp
fuente
3
No tengo idea de por qué está tratando de hacer esto, pero como regla general debería usar la detección de funciones en lugar de la detección del motor. quirksmode.org/js/support.html
Quentin
44
En realidad, se trata de una solicitud sobre cómo implementar la detección de características, pero la pregunta se describe mal.
monokrome el
publicado una biblioteca para mi propio uso, ayuda esto ayudará npmjs.com/package/detect-is-node
abhirathore2006
Un problema con la pregunta, y la mayoría de las respuestas, es la suposición de que solo hay dos posibilidades: Browser o Node.js. Existe la posibilidad de que no sea ni el navegador ni Node.js, como es el caso de Oracle Java Nashorn. Si el JDK está instalado, el comando jjs le permite ejecutar scripts. Pero hay muchas diferencias entre Nashorn y Node.js, por lo que no puede suponer nada. ¿Y quién sabe qué opciones puede traer el futuro? Se necesita la detección de características.

Respuestas:

80

Al buscar el soporte de CommonJS , así es como lo hace la biblioteca Underscore.js :

Editar: a su pregunta actualizada:

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

El ejemplo aquí retiene el patrón del Módulo.

Ross
fuente
45
Esto detecta la compatibilidad de CommonJS, que los navegadores pueden admitir.
mikemaccana
77
Aquí hay un problema y el clavador "lo clavó". Estoy probando CommonJS en el navegador, y el cargador de módulos que estoy usando define module.exports, por lo que esta solución me indicaría incorrectamente que estoy en el nodo.
Mark Melville
1
@MarkMelville podría decirse que esto es exactamente lo que pide el OP, por lo que no es un problema .
Ross
13
Mala redacción de mi parte. Quiero decir que hay un problema con esta solución. El OP puede haberlo aceptado, pero yo no.
Mark Melville
77
Esta ciertamente no es la mejor respuesta dada.
user3751385
107

Bueno, no hay una forma confiable de detectar la ejecución en Node.js ya que cada sitio web podría declarar fácilmente las mismas variables, sin embargo, dado que no hay ningún windowobjeto en Node.js de manera predeterminada, puede dar la vuelta y verificar si se está ejecutando dentro de un Navegador.

Esto es lo que uso para libs que deberían funcionar tanto en un navegador como en Node.js:

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

} else {
    window.foo = {};
}

Todavía podría explotar en caso de que windowesté definido en Node.js, pero no hay una buena razón para que alguien haga esto, ya que explícitamente necesitaría omitir varo establecer la propiedad en el globalobjeto.

EDITAR

Para detectar si su script ha sido requerido como un módulo CommonJS, eso tampoco es fácil. Lo único que CommonJS especifica es que A: los módulos se incluirán mediante una llamada a la función requirey B: los módulos exportan cosas a través de propiedades en el exportsobjeto. Ahora, cómo se implementa eso se deja al sistema subyacente. Node.js envuelve el contenido del módulo en una función anónima:

function (exports, require, module, __filename, __dirname) { 

Ver: https://github.com/ry/node/blob/master/src/node.js#L325

Pero no intente detectar eso a través de algunas arguments.callee.toString()cosas locas , solo use mi código de ejemplo anterior que verifica el navegador. Node.js es un entorno mucho más limpio, por lo que es poco probable que windowse declare allí.

Ivo Wetzel
fuente
2
Acerca de "Node.js es un entorno mucho más limpio, por lo que es poco probable que se declare la ventana allí": bueno, acabo de llegar aquí buscando una manera de averiguar si mi script se estaba ejecutando en un navegador emulado por node.js + JSDOM o en un navegador simple ... La razón es que tengo un bucle infinito usando setTimeout para verificar la ubicación de la URL, que está bien en un navegador, pero mantiene el script node.js ejecutándose para siempre ... Entonces podría haber una ventana en un script node.js después de todo :)
Eric Bréchemier
1
@Eric Dudo mucho que esté allí en el ámbito global, por lo que, a menos que importe algo como windowen la primera línea de su módulo, no debería tener ningún problema. También puede ejecutar una función anónima y comprobar el [[Class]]de thissu interior (sólo funciona en modo no estricto) Ver "clase" en: bonsaiden.github.com/JavaScript-Garden/#typeof
Ivo Wetzel
1
Mi problema difiere ligeramente de los OP: no necesito el script, lo carga JSDOM con la ventana emulada como contexto global ... Todavía lo ejecuta node.js + V8, solo en un contexto diferente al de los módulos habituales.
Eric Bréchemier 01 de
1
Probablemente ... fui en otra dirección: 1) detectar el soporte para onhashchange ("onhashchange" en la ventana) para evitar crear el bucle infinito 2) simular el soporte configurando la propiedad onhashchange en la ventana emulada en el script principal node.js.
Eric Bréchemier
1
typeof self === 'object'podría ser más seguro ya que typeof window === 'undefined'falla en el alcance de los trabajadores web.
Lewis
45

Actualmente me topé con una detección incorrecta de Node que no conoce el entorno de Node en Electron debido a una detección de características engañosa. Las siguientes soluciones identifican el entorno del proceso explícitamente.


Identificar solo Node.js

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

Esto descubrirá si está ejecutando un proceso Node, ya que process.releasecontiene los "metadatos relacionados con la versión actual [Node-]".

Después del engendro de io.js el valor de process.release.nametambién puede llegar a ser io.js(ver el documento de proceso ). Para detectar correctamente un entorno preparado para Nodo, supongo que debe verificar lo siguiente:

Identificar nodo (> = 3.0.0) o io.js

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

Esta declaración se probó con el Nodo 5.5.0, Electrón 0.36.9 (con el Nodo 5.1.1) y Chrome 48.0.2564.116.

Identificar nodo (> = 0.10.0) o io.js

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

El comentario de @daluege me inspiró a pensar en una prueba más general. Esto debería funcionar desde Node.js> = 0.10 . No encontré un identificador único para versiones anteriores.


PD: Estoy publicando esa respuesta aquí ya que la pregunta me llevó aquí, aunque el OP estaba buscando una respuesta a una pregunta diferente.

Florian Breisch
fuente
2
Este parece ser, con mucho, el enfoque más confiable, gracias. Aunque solo funciona para la versión> = 3.0.0.
filip
@daluege: gracias por la inspiración. Desafortunadamente no encontré una prueba para menos de 0.10.
Florian Breisch
3
Encontré el uso de react webpack, processy process.versionexiste dentro del paquete, por lo que agregué una verificación adicional de process.versiondónde process.release.nodeestá indefinido en el lado del cliente pero tiene una versión de nodo como valor en el lado del servidor
Aaron
@ Aaron: gracias por esa pista. No pude encontrar ninguna definición de la process.versionvariable (en react, webpack o react-webpack). Agradecería cualquier pista donde se define la variable de versión para agregarla a la respuesta. Dependiendo de las restricciones de release.node al nodo> = 3.xx
Florian Breisch
2
De una sola línea y más seguro:function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
brillout
25

El problema al tratar de averiguar en qué entorno se está ejecutando su código es que cualquier objeto puede modificarse y declararse, por lo que es casi imposible descubrir qué objetos son nativos del entorno y cuáles han sido modificados por el programa.

Sin embargo, hay algunos trucos que podemos usar para determinar con seguridad en qué entorno se encuentra.

Comencemos con la solución generalmente aceptada que se usa en la biblioteca de subrayado:

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

Esta técnica es realmente perfecta para el lado del servidor, ya que cuando requirese llama a la función, restablece el thisobjeto a un objeto vacío y lo redefine modulenuevamente, lo que significa que no tiene que preocuparse por ninguna manipulación externa. Mientras su código esté cargado, estará a requiresalvo.

Sin embargo, esto se desmorona en el navegador, ya que cualquiera puede definirlo fácilmente modulepara que parezca que es el objeto que está buscando. Por un lado, este podría ser el comportamiento que desea, pero también dicta qué variables puede usar el usuario de la biblioteca en el ámbito global. Quizás alguien quiera usar una variable con el nombre moduleque tiene exportsdentro para otro uso. Es poco probable, pero ¿quiénes somos para juzgar qué variables puede usar otra persona, solo porque otro entorno usa ese nombre de variable?

Sin embargo, el truco es que si asumimos que su script se está cargando en el ámbito global (que será si se carga a través de una etiqueta de script), una variable no se puede reservar en un cierre externo, porque el navegador no permite que . Ahora recuerde que en el nodo, el thisobjeto es un objeto vacío, sin embargo, la modulevariable aún está disponible. Eso es porque se declara en un cierre exterior. Entonces podemos arreglar el cheque de subrayado agregando un cheque adicional:

this.module !== module

Con esto, si alguien declara moduleen el alcance global en el navegador, se colocará en el thisobjeto, lo que hará que la prueba falle, porque this.moduleserá el mismo objeto que el módulo. En el nodo, this.moduleno existe y moduleexiste dentro de un cierre externo, por lo que la prueba tendrá éxito, ya que no son equivalentes.

Por lo tanto, la prueba final es:

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

Nota: Si bien esto ahora permite que la modulevariable se use libremente en el ámbito global, aún es posible evitar esto en el navegador creando un nuevo cierre y declarando moduledentro de eso, luego cargando el script dentro de ese cierre. En ese momento, el usuario está replicando completamente el entorno del nodo y, con suerte, sabe lo que está haciendo y está tratando de hacer un estilo de nodo requerido. Si se llama al código en una etiqueta de script, seguirá estando a salvo de cualquier cierre externo nuevo.

Hora
fuente
2
Wow, gracias por explicar claramente la razón detrás de cada pieza de su línea.
Jon Coombs
conseguido Cannot read property 'module' of undefinedporque esto no está definido en las pruebas de mocha, por ejemplo
srghma
20

Lo siguiente funciona en el navegador a menos que se haya saboteado intencionalmente y explícitamente:

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

Bam

usuario3751385
fuente
44
var process = {toString: function () {return '[proceso de objeto]'; }};
Nick Desaulniers
1
¿Hay alguna razón por la que usas en process+''lugar de process.toString()?
harmic
3
Casi. Use esto en su lugar:Object.prototype.toString.call(process)
sospedra
2
Esta es la mejor respuesta a esta pregunta.
loretoparisi
3
@harmic: var process = null;causaría que el segundo caso falle. Tanto en Javascript como en Java, la expresión '' + xproduce lo mismo que, x.toString()excepto cuando xes desagradable, la primera produce "null"o "undefined"donde la segunda arrojaría un error.
joeytwiddle
17

Aquí hay una manera genial de hacerlo también:

const isBrowser = this.window === this;

Esto funciona porque en los navegadores la variable global 'this' tiene una referencia propia llamada 'ventana'. Esta auto referencia no existe en el Nodo.

  • En el navegador 'esto' es una referencia al objeto global, llamado 'ventana'.
  • En el nodo 'esto' hay una referencia al objeto module.exports.
    • 'this' no es una referencia al objeto global Node, llamado 'global'.
    • 'this' no es una referencia al espacio de declaración de la variable del módulo.

Para romper la comprobación de navegador sugerida anteriormente, tendría que hacer algo como lo siguiente

this.window = this;

antes de ejecutar el cheque.

Patricio
fuente
¿Por qué no simplemente const isBrowser = this.window !== undefined? Y en teoría en el nodo que puedo hacer this.window = thispara engañar a la solución.
Tyler Long
11

Otra detección más del entorno :

(Significado: la mayoría de las respuestas aquí están bien).

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

Un poco paranoico, ¿verdad? Puede hacer esto más detallado al buscar más globales .

Pero NO!

Todos estos anteriores pueden ser falsificados / simulados de todos modos.

Por ejemplo para falsificar el globalobjeto:

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

Esto no se adjuntará al objeto global original del Nodo, pero se adjuntará al windowobjeto en un navegador. Por lo tanto, implicará que estás en Node env dentro de un navegador.

¡La vida es corta!

¿Nos importa si nuestro entorno es falso? Ocurriría cuando algún desarrollador estúpido declare una variable global llamada globalen el ámbito global. O algún malvado desarrollador inyecta código en nuestro entorno de alguna manera.

Podemos evitar que nuestro código se ejecute cuando detectemos esto, pero muchas otras dependencias de nuestra aplicación podrían quedar atrapadas en esto. Entonces eventualmente el código se romperá. Si su código es lo suficientemente bueno, no debe preocuparse por todos y cada uno de los errores tontos que podrían haber cometido otros.

¿Y qué?

Si apunta a 2 entornos: navegador y nodo;
"use strict"; y ya sea simplemente verificar windowo global; e indique claramente que en los documentos su código solo admite estos entornos. ¡Eso es!

var isBrowser = typeof window !== 'undefined'
    && ({}).toString.call(window) === '[object Window]';

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

Si es posible para su caso de uso; en lugar de la detección del entorno; hacer detección de características sincrónicas dentro de un bloque try / catch. (estos tardarán unos milisegundos en ejecutarse).

p.ej

function isPromiseSupported() {
    var supported = false;
    try {
        var p = new Promise(function (res, rej) {});
        supported = true;
    } catch (e) {}
    return supported;
}
Onur Yıldırım
fuente
9

La mayoría de las soluciones propuestas pueden ser falsificadas. Una forma robusta es verificar la Classpropiedad interna del objeto global utilizando Object.prototype.toString. La clase interna no se puede falsificar en JavaScript:

var isNode = 
    typeof global !== "undefined" && 
    {}.toString.call(global) == '[object global]';
Fabian Jakobs
fuente
2
Esto volverá a ser cierto en browserify.
alt
1
¿Probaste eso? No puedo ver cómo browserify puede cambiar la clase interna de un objeto. Esto requeriría cambiar el código en la VM de JavaScript o sobrescribir, lo Object.prototype.toStringcual es una práctica realmente mala.
Fabian Jakobs
Lo probé Esto es lo que hace browserify: var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
Vanuan
Verás, en Chrome, ({}.toString.call(window))es igual "[object global]".
Vanuan
2
Es extraño, porque window.toString()produce"[object Window]"
Vanuan
5

¿Qué pasa con el uso del objeto del proceso y comprobar execPath para node?

process.execPath

Este es el nombre de ruta absoluto del ejecutable que inició el proceso.

Ejemplo:

/ usr / local / bin / node

Kevin Hakanson
fuente
2
¿Qué hay de window.process = {execPath: "/usr/local/bin/node"};?
Константин Ван
4

Aquí está mi variación sobre lo que está arriba:

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

Para usarlo, modifique la "Casa" en la segunda última línea para que sea lo que quiera que sea el nombre del módulo en el navegador y publique lo que quiera que sea el valor del módulo (generalmente un constructor o un objeto literal )

En los navegadores, el objeto global es window, y tiene una referencia a sí mismo (hay una window.window que es == window). Me parece que esto es poco probable que ocurra a menos que esté en un navegador o en un entorno que quiera que crea que está en un navegador. En todos los demás casos, si se declara una variable global de 'módulo', la usa de lo contrario, usa el objeto global.

Kybernetikos
fuente
4

Estoy usando processpara verificar node.js así

if (typeof(process) !== 'undefined' && process.version === 'v0.9.9') {
  console.log('You are running Node.js');
} else {
  // check for browser
}

o

if (typeof(process) !== 'undefined' && process.title === 'node') {
  console.log('You are running Node.js');
} else {
  // check for browser
}

Documentado aquí

Chris
fuente
2
process.titlese puede cambiar
Ben Barkay
Luego verifique el título al que lo cambió. O use process.version
Chris
Si está escribiendo para una biblioteca (como debería), no podrá esperar cuál debería ser el título
Ben Barkay
3

Al momento de escribir este artículo, esta respuesta es más una opción "próximamente", ya que aprovecha las características muy nuevas de JavaScript.

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

El runtimevalor será nodeo not node.

Como se mencionó, esto se basa en algunas características nuevas de JavaScript. globalThises una característica finalizada en la especificación ECMAScript 2020. El motor V8, que se entrega con Chrome 80, admite el encadenamiento opcional / fusión nula ( ?parte de globalThis.process?.release?.name). A partir del 8/04/2020, este código funcionará en el navegador pero no en el Nodo ya que la rama del Nodo 13 usa V8 7.9.xxx. Creo que el Nodo 14 (que se lanzará el 21/04/2020) debe usar V8 8.x +.

Este enfoque viene con una buena dosis de restricciones actuales. Sin embargo; el ritmo al que se lanzan los navegadores / Node, con el tiempo será una línea confiable.

Corey
fuente
1
¡Esta debería ser la respuesta aceptada! y todos deberían usar el nodo 14 por cierto
Sceat
2

Node.js tiene un processobjeto, por lo tanto, siempre y cuando no tenga ningún otro script que processpueda crear , puede usarlo para determinar si el código se ejecuta en 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");
}
Dariusz Sikorski
fuente
2

Esta es una forma bastante segura y directa de garantizar la compatibilidad entre javascript del lado del servidor y del lado del cliente, que también funcionará con browserify, RequireJS o CommonJS incluidos en el lado del cliente:

(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;
}())
Christophe Marois
fuente
1

Editar : Con respecto a su pregunta actualizada: "¿Cómo puede un script saber si se ha requerido como módulo commonjs?" No creo que pueda. Puede verificar si exportses un objeto ( if (typeof exports === "object")), ya que la especificación requiere que se proporcione a los módulos, pero todo lo que le dice es que ... exportses un objeto. :-)


Respuesta original:

Estoy seguro de que hay algún símbolo específico de NodeJS ( EventEmittertal vez no, debe usarlo requirepara obtener el módulo de eventos; ver más abajo ) que puede verificar, pero como dijo David, lo ideal es que detecte la función (más bien que el medio ambiente) si tiene sentido hacerlo.

Actualización : tal vez algo como:

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

Pero eso solo te dice que estás en un entorno con requirealgo muy, muy parecido a NodeJS Buffer. :-)

TJ Crowder
fuente
Todavía puedo romper eso configurando todas esas cosas en un sitio web ... eso es exagerado;) Verificar que esté en un navegador es más fácil ya que el entorno Node es más limpio.
Ivo Wetzel el
1
@Ivo: Sí, mira mi última oración. windowIgualmente puedo romper su verificación definiendo una variable dentro de una aplicación NodeJS. :-)
TJ Crowder
1
@Ivo: No me sorprendería en absoluto si alguien definiera windowen un módulo NodeJS, por lo que podrían incluir código que se basara en windowser el objeto global y no quisiera modificar ese código. Yo no lo haría, no, pero apuesto a que alguien lo ha hecho. :-) O simplemente windowsignificaban algo completamente diferente.
TJ Crowder
1
@Ivo: yuiblog.com/blog/2010/04/09/… es una de las razones por las que el objeto de ventana se puede definir en node.js
slebetman
1
@TJCrowdertypeof process !== "undefined" && process.title === "node"
Raynos
0
const isNode =
  typeof process !== 'undefined' &&
  process.versions != null &&
  process.versions.node != null;
BAR
fuente
-1

Tome la fuente de node.js y cámbiela para definir una variable como runningOnNodeJS. Verifique esa variable en su código.

Si no puede tener su propia versión privada de node.js, abra una solicitud de función en el proyecto. Pídales que definan una variable que le proporcione la versión de node.js en la que se está ejecutando. Luego, verifique esa variable.

Aaron Digulla
fuente
1
Eso de nuevo no resuelve su problema (básicamente insoluble), nuevamente puedo crear una variable de este tipo en el navegador. Una mejor manera sería evitar que los scripts creen un windowglobal, supongo que presentaré una solicitud de función en ese.
Ivo Wetzel el
@Ivo: Esa es una mala idea que rompería el código que usa jsdom ( github.com/tmpvar/jsdom ) para hacer la manipulación dom del lado del servidor usando bibliotecas familiares como YUI y jQuery. Y actualmente hay un código en producción que hace esto.
slebetman
@Slebetman No, no romperá jsdom. Estoy hablando de global , como en ninguna declaración var global , el código de ejemplo allí usa la vardeclaración, las personas que simplemente la filtran en el espacio de nombres global, bueno, entonces no entienden el concepto de módulos autónomos
Ivo Wetzel
@Ivo es un poco violento, es como decir que deberíamos tomar la capacidad de comer pasteles porque la gente engorda comiendo en exceso. Usted debe saturar el espacio de nombres global para lograr una biblioteca que funcionará entre módulos. O podría envolverlo todo en un solo módulo, pero ¿cuál es el punto?
Ben Barkay
-1

Publicación muy antigua, pero la resolví envolviendo las declaraciones requeridas en un intento - captura

try {
     var fs = require('fs')
} catch(e) {
     alert('you are not in node !!!')
}
Stef de Vries
fuente
2
Eso no es cierto, puede usar browserify para usar "requireish" require () llamadas
gordo