Como verificar se um script está sendo executado no Node.js?

159

Eu tenho um script que estou exigindo de um script Node.js, que desejo manter independente do mecanismo JavaScript.

Por exemplo, eu quero fazer exports.x = y;apenas se estiver sendo executado em Node.js. Como posso realizar este teste?


Ao postar esta pergunta, eu não sabia que o recurso de módulos Node.js se baseia no CommonJS .

Para o exemplo específico que dei, uma pergunta mais precisa teria sido:

Como um script pode dizer se foi necessário como um módulo CommonJS?

theosp
fonte
3
Não sei por que você está tentando fazer isso, mas como regra geral, você deve usar a detecção de recursos em vez da detecção do mecanismo. Quirksmode.org/js/support.html
Quentin
4
Este é realmente um pedido sobre como implementar a detecção de recursos, mas a pergunta se descreve mal.
monokrome 23/01
publicou uma biblioteca para meu próprio uso, ajuda isso vai ajudar npmjs.com/package/detect-is-node
abhirathore2006
Um problema com a pergunta e a maioria das respostas é a suposição de que existem apenas duas possibilidades: Navegador ou Node.js. Existe a possibilidade de que ele não seja navegador nem Node.js, como é o caso do Oracle Java Nashorn. Se o JDK estiver instalado, o comando jjs permitirá executar scripts. Mas há muitas diferenças entre Nashorn e Node.js, portanto você não pode fazer nenhuma suposição. E quem sabe que opções o futuro pode trazer? A detecção de recursos é necessária.

Respostas:

80

Ao procurar o suporte do CommonJS , é assim que a biblioteca Underscore.js faz isso:

Editar: para sua pergunta atualizada:

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

O exemplo aqui mantém o padrão do módulo.

Ross
fonte
45
Isso detecta o suporte ao CommonJS, que navegadores podem suportar.
Mikemaccana
7
Há um problema aqui e o pregador "acertou em cheio". Estou tentando o CommonJS no navegador e o carregador de módulo que estou usando define module.exports, portanto, esta solução me dizia incorretamente que estou no nó.
Mark-Melville-
1
@ MarkMelville, sem dúvida, é exatamente isso que o OP está pedindo, portanto, não é um problema .
Ross
13
Palavras pobres da minha parte. Quero dizer, há um problema com esta solução. O OP pode ter aceitado, mas eu não.
Mark Melville
7
Esta certamente não é a melhor resposta dada.
user3751385
107

Bem, não há uma maneira confiável de detectar a execução no Node.js. Como todo site pode facilmente declarar as mesmas variáveis, ainda assim, como não há windowobjeto no Node.js por padrão, você pode fazer o contrário e verificar se está executando dentro de um Node.js. Navegador.

É isso que eu uso para bibliotecas que devem funcionar tanto em um navegador quanto em Node.js:

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

} else {
    window.foo = {};
}

Ele ainda pode explodir caso windowseja definido no Node.js, mas não há uma boa razão para alguém fazer isso, pois você precisaria explicitamente deixar de fora varou definir a propriedade no globalobjeto.

EDITAR

Para detectar se seu script foi necessário como um módulo CommonJS, isso também não é fácil. A única coisa que o commonJS especifica é que A: Os módulos serão incluídos por meio de uma chamada para a função requiree B: Os módulos exportam itens por meio de propriedades no exportsobjeto. Agora, como isso é implementado, é deixado para o sistema subjacente. O Node.js agrupa o conteúdo do módulo em uma função anônima:

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

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

Mas não tente detectar isso por meio de arguments.callee.toString()coisas malucas , basta usar meu código de exemplo acima, que verifica o navegador. O Node.js é um ambiente muito mais limpo, por isso é improvável que windowseja declarado lá.

Ivo Wetzel
fonte
2
Sobre "O Node.js é um ambiente muito mais limpo, por isso é improvável que a janela seja declarada lá.": Bem, eu vim aqui procurando uma maneira de descobrir se meu script estava sendo executado em um navegador emulado pelo node.js + JSDOM ou em um navegador comum ... O motivo é que eu tenho um loop infinito usando setTimeout para verificar o local da URL, o que é bom em um navegador, mas mantém o script node.js em execução para sempre ... Portanto, pode haver uma janela em um script node.js, afinal :) :)
Eric Bréchemier
1
@ Eric duvido que esteja lá no escopo global, portanto, a menos que você importe algo como windowna primeira linha do seu módulo, não deverá ter nenhum problema. Você também pode executar uma função anônima e verifique o [[Class]]de thisdentro dele (funciona apenas no modo não-estrito) Consulte "Classe" sob: bonsaiden.github.com/JavaScript-Garden/#typeof
Ivo Wetzel
1
Meu problema difere um pouco dos OP: não estou exigindo o script, ele é carregado pelo JSDOM com a janela emulada como contexto global ... Ele ainda é executado pelo node.js + V8, apenas em um contexto diferente dos módulos usuais.
Eric Bréchemier
1
Provavelmente ... Fui outra direção: 1) detectar o suporte ao onhashchange ("onhashchange" na janela) para evitar a criação do loop infinito 2) simular o suporte configurando a propriedade onhashchange na janela emulada no script node.js principal.
Eric Bréchemier
1
typeof self === 'object'pode ser mais seguro, pois typeof window === 'undefined'falha no escopo dos trabalhadores da Web.
Lewis
45

No momento, deparei-me com uma detecção incorreta do nó que não está ciente do ambiente do nó no Electron devido a uma detecção enganosa de recursos. As soluções a seguir identificam o ambiente do processo explicitamente.


Identifique apenas o Node.js

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

Isso descobrirá se você está executando um processo de nó, pois process.release contém os "metadados relacionados à versão atual do [Node-]".

Após o surgimento de io.js, o valor de process.release.nametambém pode se tornar io.js(consulte o documento do processo ). Para detectar corretamente um ambiente pronto para o nó, acho que você deve verificar da seguinte maneira:

Identifique o nó (> = 3.0.0) ou io.js

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

Esta declaração foi testada com o Nó 5.5.0, Electron 0.36.9 (com Nó 5.1.1) e Chrome 48.0.2564.116.

Identifique o nó (> = 0.10.0) ou io.js

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

O comentário de @ daluege me inspirou a pensar em uma prova mais geral. Isso deve funcionar no Node.js> = 0.10 . Não encontrei um identificador exclusivo para versões anteriores.


Ps: Estou postando essa resposta aqui, pois a pergunta me levou até aqui, embora o OP estivesse procurando uma resposta para uma pergunta diferente.

Florian Breisch
fonte
2
Essa parece ser de longe a abordagem mais confiável, obrigado. Embora esteja trabalhando apenas para a versão> = 3.0.0.
filip
@daluege - obrigado pela inspiração. Infelizmente, não encontrei uma prova inferior a 0,10.
Florian Breisch 28/10
3
Eu encontrei usando reagir Webpack, processe process.versionexiste dentro do pacote, então eu adicionei uma verificação extra para process.versiononde process.release.nodeé indefinido no lado do cliente, mas tem uma versão nó como um valor no lado do servidor
Aaron
@ Aaron: obrigado por essa dica. Não consegui encontrar nenhuma definição da process.versionvariável (em react, webpack ou react-webpack). Gostaria muito de receber uma dica de onde a variável de versão está definida para adicioná-la à resposta. Dependendo das restrições release.node para o nó> = 3.xx
Florian Breisch
2
Uma linha e mais seguro:function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }
brillout 14/10
25

O problema de tentar descobrir em que ambiente seu código está executando é que qualquer objeto pode ser modificado e declarado, tornando quase impossível descobrir quais objetos são nativos do ambiente e quais foram modificados pelo programa.

No entanto, existem alguns truques que podemos usar para descobrir com certeza em que ambiente você está.

Vamos começar com a solução geralmente aceita usada na biblioteca de sublinhados:

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

Essa técnica é realmente perfeita para o servidor, pois quando a requirefunção é chamada, ela redefine o thisobjeto para um objeto vazio e redefine modulepara você novamente, o que significa que você não precisa se preocupar com adulterações externas. Enquanto seu código estiver carregado require, você estará seguro.

No entanto, isso desmorona no navegador, pois qualquer um pode definir facilmente modulepara fazer parecer que é o objeto que você está procurando. Por um lado, esse pode ser o comportamento que você deseja, mas também determina quais variáveis ​​o usuário da biblioteca pode usar no escopo global. Talvez alguém queira usar uma variável com o nome moduleque possuiexports dentro dele para outro uso. É improvável, mas quem somos nós para julgar quais variáveis ​​alguém pode usar, apenas porque outro ambiente usa esse nome de variável?

O truque, no entanto, é que, se estivermos assumindo que seu script está sendo carregado no escopo global (o que acontecerá se for carregado por meio de uma tag de script), uma variável não poderá ser reservada em um fechamento externo, porque o navegador não permite que . Agora lembre-se, no nó, o thisobjeto é um objeto vazio, mas a modulevariável ainda está disponível. Isso é porque é declarado em um fechamento externo. Assim, podemos corrigir a verificação do sublinhado adicionando uma verificação extra:

this.module !== module

Com isso, se alguém declarar moduleno escopo global no navegador, ele será colocado no thisobjeto, o que fará com que o teste falhe, porque this.moduleserá o mesmo objeto que o módulo. No nó, this.modulenão existe e moduleexiste dentro de um fechamento externo; portanto, o teste será bem-sucedido, pois não são equivalentes.

Assim, o teste final é:

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

Nota: Embora agora isso permita que a modulevariável seja usada livremente no escopo global, ainda é possível ignorá-la no navegador, criando um novo fechamento e declarando moduledentro disso, carregando o script nesse encerramento. Nesse ponto, o usuário está replicando completamente o ambiente do nó e, esperançosamente, sabe o que está fazendo e está tentando fazer um estilo de nó exigir. Se o código for chamado em uma tag de script, ele ainda estará protegido contra novos fechamentos externos.

Tempo
fonte
2
Uau, obrigado por explicar claramente a lógica por trás de cada peça da sua linha.
Jon Coombs
obtido Cannot read property 'module' of undefinedporque este é indefinido nos testes mocha por exemplo
srghma
20

O seguinte funciona no navegador, a menos que seja intencional e explicitamente sabotado:

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

Bam.

user3751385
fonte
4
var processo = {toString: function () {return '[processo do objeto]'; }};
Nick Desaulniers
1
Existe alguma razão para você usar em process+''vez de process.toString()?
harmic
3
Quase. Use isto:Object.prototype.toString.call(process)
sospedra
2
Esta é a melhor resposta para esta pergunta.
31416 loretoparisi
3
@harmic: var process = null;causaria falha no segundo caso. Em Javascript e Java, a expressão '' + xproduz o mesmo que x.toString()exceto quando xé desagradável, o primeiro produz "null"ou "undefined"onde o último geraria um erro.
joeytwiddle
17

Aqui está uma maneira bem legal de fazer isso:

const isBrowser = this.window === this;

Isso funciona porque em navegadores a variável global 'this' possui uma auto-referência chamada 'window'. Essa referência própria não existe no Nó.

  • No navegador 'this' é uma referência ao objeto global, chamado 'window'.
  • No nó 'this' é uma referência ao objeto module.exports.
    • 'isso' não é uma referência ao objeto global do Nó, chamado 'global'.
    • 'this' não é uma referência ao espaço de declaração da variável do módulo.

Para interromper a verificação do navegador sugerida acima, você teria que fazer algo como o seguinte

this.window = this;

antes de executar a verificação.

Patrick
fonte
Por que não simplesmente const isBrowser = this.window !== undefined? E, em teoria, no nó eu posso fazer this.window = thispara enganar a solução.
Tyler Long
11

Mais uma detecção de ambiente :

(Significado: a maioria das respostas aqui está correta.)

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

Um pouco paranóico, certo? Você pode tornar isso mais detalhado, verificando se há mais globais .

Mas NÃO!

Todos esses itens acima podem ser falsificados / simulados de qualquer maneira.

Por exemplo, para falsificar o globalobjeto:

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

Isso não será anexado ao objeto global original do Nó, mas será anexado ao window objeto em um navegador. Portanto, isso implica que você está no Node env dentro de um navegador.

A vida é curta!

Nos importamos se nosso ambiente é falso? Isso aconteceria quando algum desenvolvedor estúpido declarasse uma variável global chamadaglobal no escopo global. Ou algum desenvolvedor mal injeta código em nosso ambiente de alguma forma.

Podemos impedir que nosso código seja executado quando detectamos isso, mas muitas outras dependências do nosso aplicativo podem ficar presas a ele. Então, eventualmente, o código será quebrado. Se o seu código for bom o suficiente, você não deve se importar com todos os erros tolos que poderiam ter sido cometidos por outras pessoas.

E daí?

Se estiver direcionando 2 ambientes: Navegador e Nó;
"use strict"; e simplesmente verifique windowou global; e indique claramente nos documentos que o seu código suporta apenas esses ambientes. É isso aí!

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

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

Se possível para o seu caso de uso; em vez de detecção de ambiente; faça a detecção de recurso síncrono dentro de um bloco try / catch. (isso levará alguns milissegundos para ser executado).

por exemplo

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

A maioria das soluções propostas pode realmente ser falsificada. Uma maneira robusta é verificar a Classpropriedade interna do objeto global usando o Object.prototype.toString. A classe interna não pode ser falsificada em JavaScript:

var isNode = 
    typeof global !== "undefined" && 
    {}.toString.call(global) == '[object global]';
Fabian Jakobs
fonte
2
Isso voltará a ser verdade sob o browserify.
alt
1
Você testou isso? Não vejo como o browserify pode alterar a classe interna de um objeto. Isso exigiria a alteração do código na VM JavaScript ou a substituição, o Object.prototype.toStringque é realmente uma prática ruim.
Fabian Jakobs
Eu testei. Aqui está o que browserify faz: var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
Vanuan
Você vê, no Chrome, ({}.toString.call(window))é igual "[object global]".
Vanuan
2
É estranho, porque window.toString()produz"[object Window]"
Vanuan
5

Que tal usar o objeto de processo e verificar execPath para node?

process.execPath

Este é o nome do caminho absoluto do executável que iniciou o processo.

Exemplo:

/ usr / local / bin / node

Kevin Hakanson
fonte
2
Que tal window.process = {execPath: "/usr/local/bin/node"};?
precisa saber é o seguinte
4

Aqui está minha variação do que está acima:

(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 usá-lo, você modifica o "House" na segunda última linha para ser o que você deseja que o nome do módulo esteja no navegador e publica o que você deseja que o valor do módulo seja (geralmente um construtor ou um objeto literal )

Nos navegadores, o objeto global é window e tem uma referência a si mesmo (há uma window.window que é == window). Parece-me que é improvável que isso ocorra, a menos que você esteja em um navegador ou em um ambiente que queira que você acredite que está em um navegador. Em todos os outros casos, se houver uma variável global 'module' declarada, ela será usada, caso contrário, será usada o objeto global.

kybernetikos
fonte
4

Estou usando processpara verificar se há node.js assim

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

ou

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

Documentado aqui

Chris
fonte
2
process.titlepode ser alterado
Ben Barkay
Em seguida, verifique o título para o qual você o alterou. Ou o uso process.version
Chris
Se você está escrevendo para uma biblioteca (como você deve), você não será capaz de esperar que o título deve ser
Ben Barkay
3

No momento da redação deste artigo, essa resposta é mais uma opção "em breve", pois utiliza recursos muito novos do JavaScript.

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

o runtime valor seránode ou not node.

Como mencionado, isso depende de alguns novos recursos JavaScript. globalThisé um recurso finalizado na especificação ECMAScript 2020. Encadeamento opcional / coalescência nula (a ?parte deglobalThis.process?.release?.name ) é suportado no mecanismo V8, que é fornecido com o Chrome 80. A partir de 8/4/2020, esse código funcionará no navegador, mas não funcionará no Nó, pois a ramificação do Nó 13 usa V8 7.9.xxx. Acredito que o Nó 14 (com lançamento previsto para 21/4/2020) deve usar o V8 8.x +.

Essa abordagem vem com uma dose saudável das restrições atuais. Contudo; o ritmo em que os navegadores / Node são lançados, acabará sendo um one-liner confiável.

Corey
fonte
1
Essa deve ser a resposta aceita! e todos devem usar o nó 14 btw
Sceat
2

O Node.js possui um processobjeto, desde que você não tenha nenhum outro script que o crie. processVocê pode usá-lo para determinar se o código é executado no 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
fonte
2

Essa é uma maneira bastante segura e direta de garantir a compatibilidade entre o javascript do lado do servidor e do lado do cliente, que também funcionará com o browserify, RequireJS ou CommonJS incluído no lado do 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
fonte
1

Editar : em relação à sua pergunta atualizada: "Como um script pode dizer se foi necessário como um módulo commonjs?" Eu não acho que pode. Você pode verificar se exportsé um objeto ( if (typeof exports === "object")), já que a especificação exige que seja fornecida aos módulos, mas tudo o que indica é que ... exportsé um objeto. :-)


Resposta original:

Tenho certeza de que há algum símbolo específico do NodeJS ( EventEmittertalvez não, você precisa usar requirepara obter o módulo de eventos; veja abaixo ) que você poderia procurar, mas, como David disse, o ideal é você detectar melhor o recurso (em vez ambiente) se faz algum sentido fazê-lo.

Atualização : Talvez algo como:

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

Mas isso apenas indica que você está em um ambiente com requirealgo muito parecido com o NodeJS Buffer. :-)

TJ Crowder
fonte
Ainda posso quebrar isso configurando tudo isso em um site ... isso é um exagero;) Verificar a presença de um navegador é mais fácil, pois o ambiente do Node é mais limpo.
Ivo Wetzel
1
@ Ivo: Sim, veja minha última frase. Posso facilmente quebrar sua verificação definindo uma windowvariável dentro de um aplicativo NodeJS. :-)
TJ Crowder
1
@ Ivo: Eu não seria de todo surpreso se alguém definido windowem um módulo NodeJS, para que eles pudessem incluir um código que dependia de windowser o objeto global e não queria modificar esse código. Eu não faria, você não faria, mas aposto que alguém fez. :-) Ou eles costumavam windowsignificar algo completamente diferente.
TJ Crowder
1
@ Ivo: yuiblog.com/blog/2010/04/09/... é uma das razões por que o objecto de janela pode ser definido em node.js
slebetman
1
@TJCrowdertypeof process !== "undefined" && process.title === "node"
Raynos
0
const isNode =
  typeof process !== 'undefined' &&
  process.versions != null &&
  process.versions.node != null;
BARRA
fonte
-1

Pegue a fonte do node.js e altere-a para definir uma variável como runningOnNodeJS. Verifique essa variável no seu código.

Se você não pode ter sua própria versão privada do node.js, abra uma solicitação de recurso no projeto. Peça que eles definam uma variável que fornece a versão do node.js em que você está executando. Em seguida, verifique essa variável.

Aaron Digulla
fonte
1
Isso, novamente, não resolve o problema (basicamente insolúvel), posso novamente criar uma variável no navegador. Melhor maneira seria impedir que scripts criassem um windowglobal, acho que vou registrar uma solicitação de recurso nesse.
Ivo Wetzel
@ Ivo: Essa é uma péssima idéia que quebraria o código que usa o jsdom ( github.com/tmpvar/jsdom ) para manipular o domínio do lado do servidor usando bibliotecas conhecidas como YUI e jQuery. E há código atualmente em produção que faz isso.
Slebetman
@slebetman Não, não vai quebrar jsdom. Estou falando de global , como em nenhuma instrução var global , o código de exemplo lá usa a varinstrução, pessoas que simplesmente a lançam no espaço de nomes global, então elas não entendem o conceito de módulos independentes
Ivo Wetzel
@ Ivo isso é meio violento, é como dizer que devemos comer bolos porque as pessoas engordam demais. Você deve desordenar o espaço para nome global para obter uma biblioteca que funcione entre módulos. Ou você pode agrupar tudo em um único módulo, mas qual é o objetivo?
precisa saber é o seguinte
-1

Post muito antigo, mas eu apenas o resolvi envolvendo as instruções de solicitação em uma tentativa - captura

try {
     var fs = require('fs')
} catch(e) {
     alert('you are not in node !!!')
}
Stef de Vries
fonte
2
Isso não é verdade, você pode usar o browserify para usar chamadas "nodeish" require ()
fat