88

I'm developping a JS-app that needs to work both on the client side and the server side (in Javascript on a browser and in Node.js), and I would like to be able to reuse the parts of the code that are used for both sides.

I have found out that window was a variable only accessible on Browsers, and global in node, so I can detect in which environment the code is executing (assuming that no script declares the window variable)

They are two problems.

  1. How should I detect in which browser the code is running. For example, is this code OK. (This code is inline, meaning that it is surrounded by some global code, reused for both environments)

    if window?
        totalPath= "../examples/#{path}"
    else
        totalPath= "../../examples/#{path}"
    
  2. How can I use global variables for both environments ? Now, I'm doing the following, but this really doesn't feel right.

    if window?
        window.DocUtils = {}
        window.docX = []
        window.docXData= []
    else
        global.DocUtils= {}
        global.docX = []
        global.docXData = []
    
edi9999
  • 19,701
  • 13
  • 88
  • 127
  • Possible duplicate of [How to check whether a script is running under node.js?](https://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js) – João Pimentel Ferreira Feb 09 '18 at 16:28

11 Answers11

117

NOTE: This question had two parts, but because the title was "Environment detection: node.js or browser" - I will get to this part first, because I guess many people are coming here to look for an answer to that. A separate question might be in order.

In JavaScript variables can be redefined by the inner scopes, thus assuming that environment has not created variables named as process, global or window could easily fail, for example if one is using node.js jsdom module, the API usage example has

var window = doc.defaultView;

After which detecting the environment based on the existence of window variable would systematically fail by any module running under that scope. With the same logic any browser based code could easily overwrite global or process, because they are not reserved variables in that environment.

Fortunately there is a way of requiring the global scope and testing what it is - if you create a new function using a new Function() constructor, the execution scope of this is bound to the global scope and you can compare the global scope directly to the expected value. *)

So to create a function check if the global scope is "window" would be

var isBrowser=new Function("try {return this===window;}catch(e){ return false;}");

// tests if global scope is bound to window
if(isBrowser()) console.log("running under browser");

And function to test if global scope is bound to "global" would be

var isNode=new Function("try {return this===global;}catch(e){return false;}");

// tests if global scope is bound to "global"
if(isNode()) console.log("running under node.js");

the try... catch -part will makes sure that if variable is not defined, false is returned.

The isNode()could also compare this.process.title==="node" or some other global scope variable found inside node.js if you will, but comparing to the global should be enough in practice.

http://jsfiddle.net/p6yedbqk/

NOTE: detecting the running environment is not recommended. However, it can be useful in a specific environment, like development and testing environment which has some known characteristics for the global scope.

Now - the second part of the answer. after the environment detection has been done, you can select which environment based strategy you want to use (if any) to bind your variable which are "global" to your application.

The recommended strategy here, in my opinion, would be to use a singleton pattern to bind your settings inside a class. There is a good list of alternatives already in SO

Simplest/cleanest way to implement a singleton in JavaScript

So, it may turn out if you do not need a "global" variable, and you do not need the environment detection at all, just use the singleton pattern to defined a module, which will store the values for you. OK, one can argue that the module itself is a global variable, which in JavaScript it actually is, but at least in theory it looks a bit cleaner way of doing it.

*) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function

Note: Functions created with the Function constructor do not create closures to their creation contexts; they always are created in the global scope. When running them, they will only be able to access their own local variables and global ones, not the ones from the scope in which the Function constructor was called.

Richard Hansen
  • 51,690
  • 20
  • 90
  • 97
Tero Tolonen
  • 4,144
  • 4
  • 27
  • 32
  • 2
    hi, thanks for providing this solution, i tried many things but this one worked just perfect, so i published a npm package using this, i hope you don't mind it... check it out https://www.npmjs.com/package/detect-is-node – abhirathore2006 Jul 31 '16 at 06:12
  • @abhirathore2006 very minimalistic ;) could be one-liner still with, module.exports = (new Function("try {return this===global;}catch(e){return false;}"))(); – Tero Tolonen Jul 31 '16 at 09:39
  • 1
    yes i agree, but if you want to use it 100 times then it becomes easy to read and understand a simple function. – abhirathore2006 Aug 01 '16 at 05:01
  • @TeroTolonen, why do you say it's not recommended to detect an environment? Many major frameworks work in both node and web environments. A module that includes a 'get file' function will work very differently in each environment e.g. XMLHttpRequest for web, http or path modules for Node. – McShaman Oct 08 '17 at 21:08
  • 2
    Without exceptions: `(function (){ return (typeof window !== 'undefined') && (this === window); }).call(undefined);` – Semmel Jan 24 '18 at 21:25
  • @Semmel good point and perhaps even `(function(){return typeof window !== 'undefined' && this===window})();` – Tero Tolonen Jan 25 '18 at 17:11
  • `this` is not the `gobal` if you use babel to transpile. – Riceball LEE Sep 15 '18 at 03:31
  • With all the different JS execution environments we have these days, I started a module to attempt to differentiate these. https://www.npmjs.com/package/environ. Setting up the testing is the tough part. – kjs3 Oct 26 '20 at 16:21
  • 1
    I believe that a limitation of the `new Function()` constructor approach is that it fails if 'unsafe-eval' is not an allowed source of script in the Content Security Policy script-src directive. – John Henderson Jan 04 '21 at 19:17
  • @JohnHenderson was about to write about this, it seems that the function constructor is redundant it works fine without it (using IIFE). – jcubic Feb 24 '21 at 11:45
25

Since apparently Node.js could have both (w/ NW.js?), my personnal way to do it is by detecting if the node entry exists in process.versions object.

var isNode = false;    
if (typeof process === 'object') {
  if (typeof process.versions === 'object') {
    if (typeof process.versions.node !== 'undefined') {
      isNode = true;
    }
  }
}

The multilevel of conditions is to avoid errors while searching into an undefined variable due to some browsers limitations.

Reference: https://nodejs.org/api/process.html#process_process_versions

Dan. B.
  • 291
  • 3
  • 3
  • 6
    One-liner: `function isNodejs() { return typeof "process" !== "undefined" && process && process.versions && process.versions.node; }` – brillout Oct 14 '18 at 17:47
  • @brillout Close: `function isNodejs() { return typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node !== 'undefined'; }` – ams Feb 20 '19 at 08:29
  • 1
    `process.versions.node !== 'undefined'` should be `process.versions.node !== undefined` – avalanche1 Aug 10 '20 at 19:47
  • @avalanche1 note that it's using `typeof` so it's fine. – David Sherret May 01 '21 at 17:47
  • 3
    In modern syntax: `const isNode = typeof process !== "undefined" && process?.versions?.node;` – Nixinova Jun 17 '21 at 07:32
14

There is an npm package just for this and it can be used both on client-side and server-side.

browser-or-node

You can use it this way

if (isBrowser) {
  // do browser only stuff
}

if (isNode) {
  // do node.js only stuff
}

Disclaimer: I am the author of this package :)

Dinesh Pandiyan
  • 5,814
  • 2
  • 30
  • 49
9

I know this is a late answer to a (1.5 year) old question but why not copy jQuery's source code?

if (typeof module === "object" && typeof module.exports === "object") {
  // node
}

if (typeof window !== "undefined" && typeof window.document !== "undefined") {
  // browser
}

Good luck.

Malekai
  • 4,765
  • 5
  • 25
  • 60
  • 1
    I am wondering when is `window` defined but `window.document` is not – nonopolarity Dec 14 '19 at 03:55
  • 1
    @nonopolarity If you don't check the `window.document` and your nodejs has a `window` variable defined, you'll get a false positive for browser. – Salatiel May 22 '21 at 16:05
8

You can attach to variable window or global - based on situation. Though it is not a recommended way of making multi-platform JS application:

var app = window ? window : global;

It is much better to have your global variable, that will be used over logic of application, but will be made of parts of based on different platforms. Something like:

var app = {
    env: '',
    agent: ''
};

if (window) {
    app.env = 'browser';
    app.agent = navigator.userAgent;
} else if (process) {
    app.env = 'node';
}

So the idea is that your main application logic will be absolutely the same and will use same object, only that global object have to be changed based on environment. That makes your application much more portable and flexible in terms of platforms.

moka
  • 22,846
  • 4
  • 51
  • 67
  • Thanks, actually I found an other post that handled the coffeescript part http://stackoverflow.com/questions/4214731/coffeescript-global-variables, so this is great – edi9999 Jul 11 '13 at 08:42
  • Note that `var app = window ? window : global;` can be simplified to `var app = window || global;`. – Itai Steinherz Jan 27 '19 at 11:41
  • 5
    In node `var app = window ? window : global;` produces `Thrown: ReferenceError: window is not defined`. Did you test it to work in node? (version used `v11.15.0`) – humanityANDpeace Jul 02 '19 at 11:43
  • `var app = (typeof(window) != 'undefined') ? window : global` seems to work – Nathan Chappell Jan 29 '20 at 21:08
5

this seems to work well regardless of scope unless you've named something else window

const isBrowser = () => typeof window !== `undefined`

if (isBrowser())
  console.log(`is browser`)
else
  console.log(`is node.js`)
Aronanda
  • 191
  • 1
  • 2
  • 3
1
/*
    detect global/window/self in browser, deno, nodejs
    including where 'this' is undefined
*/

const self = new Function('return this')(); // be careful, Function is like eval, use with caution
console.log( 
    (self.window && "window" || self.global && 'global'),
    self.toString().slice('[object '.length, -1).toLowerCase()
);

/*
    browser: window window
    nodejs: global global
    deno: window object
*/
jimmont
  • 2,304
  • 1
  • 27
  • 29
1

Old question with many complicated answers, even an npm package, but this solution is quite simple and robust, unless sabotaged on purpose (no solution is 100% precise BTW, because you can set global variables on both environments)

if (typeof process === 'object' && String(process) === '[object process]') {
  // is Node
} else {
  // is Browser
}

Normally (almost always) scripts which run on browsers don't have the global process object, and even if you create one by accident with process = {}, it will fail in the second condition.

João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
0

I am not totally familiar with the Node environment and all its situations such as when Babel or WebPack is being used. But this is one way if you have code that runs in the browser vs in the Node console:

if (this.window) {
  // inside browser

} else {
  // inside Node

}
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
0

Simple condition from pdf.js

Second condition variant process.constructor.name === 'process'

src/shared/is_node.js:

/* Copyright 2018 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* globals process */

// NW.js / Electron is a browser context, but copies some Node.js objects; see
// http://docs.nwjs.io/en/latest/For%20Users/Advanced/JavaScript%20Contexts%20in%20NW.js/#access-nodejs-and-nwjs-api-in-browser-context
// https://www.electronjs.org/docs/api/process#processversionselectron-readonly
// https://www.electronjs.org/docs/api/process#processtype-readonly
const isNodeJS =
  typeof process === "object" &&
  process + "" === "[object process]" &&
  !process.versions.nw &&
  !(process.versions.electron && process.type && process.type !== "browser");

export { isNodeJS }; 
Jan
  • 2,178
  • 3
  • 14
  • 26
0

This is what I'm using based on @TeroTolonen answer:

var isBrowser = (function() {
  try {
    return this === window;
  } catch (e) {
    return false;
  }
})();

if (isBrowser) {

}

There is no need for a function constructor and you can call it once.

jcubic
  • 61,973
  • 54
  • 229
  • 402