Tim Etler

How to safely determine if javascript is running in node.js or the browser

October 13, 2012

While working on a javascript library, intended for use on both the server and the client, I ran across an interesting problem; how do you reliably detect what javascript environment your script is running in? I looked around and could not find a solution I was satisfied with, so I decided to look into the problem further.

The problem is that any variable can be freely declared and modified, making it very hard to figure out which variables are native to the environment, and which have been modified by earlier scripts. While looking into the problem I focused on which environment states could be relied on, and which couldn’t.

In node.js, libraries are loaded using the require function to load modules by name. In the library module file, objects defined within the script are exposed to node.js by assigning variables to the exports object that is made available to you by node. The exports variable can also be referenced through the module variable, as module.exports.

It turns out, on the server side, you can safely assume that the exports variable is referencing the correct object. This is because the require function defines module and exports within a closure for that function, and redefines this to be an empty object. If your code is only loaded through the require function, it is safe to assume that the external script environment variables are referencing the correct node.js objects, regardless of the modifications made to the global scope by any other programmer.

If your library is only designed to be run on the server side, there’s no need to do any environment checking at all. However, if you want to create a library for use on both the server and client it is desirable to provide the same script to both for simplicity and ease of maintainability.

Underscore is a library that faces this problem, and it provides a duck typing solution:

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

This technique simply checks if module exists and if that object has exports within it. At that point it is just assumed that module is referencing an object with the correct behavior, because that object is similar enough in form to the one we were expecting, and the nested variable access makes the chance of variable conflicts less likely by expecting a more obscure variable structure.

While this technique is good enough for the most part, it does have the drawback that the end developer cannot define an object referenced by module that has an exports variable. Ok, sure that’s pretty unlikely. However, as good programmers we’re taught not to pollute the global scope, and assuming reserved variable names is just as bad.

Why should module.exports be reserved throughout the entire language simply because a popular environment uses it as well? It would be more correct, and courteous, if we made no restrictions on what variable names the end developer can use, even if they are obscure.

We can get around this by knowing a few things, and by assuming that your script is being loaded in the global scope (which it will be if it’s loaded via a script tag). A variable cannot be reserved in an outer closure, because the script is running in the outermost closure made available by the browser. Also, the reference made by this cannot be modified, because this is actually a keyword, and not a variable. Now remember in node, the this object references an empty object, yet the module and exports variables are still available. That is because it’s declared in an outer closure. So we can then fix underscore’s check by changing it to:

    this.exports !== exports

With this, if someone declares exports in the global scope in the browser, it will be placed in the this object. This will cause the test to fail, because this.exports, will be the same object as exports. On node, this.exports does not exist, and exports exists within an outer closure, so the test will succeed. We can check the exports variable directly because we are no longer relying on the obscurity of the variable structure.

Thus, the final test is:

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

While this now allows the exports variable to be used freely in the global scope, it is still possible to bypass this on the browser by creating a new closure and declaring exports within that, then loading the script within that closure. At that point the user is fully replicating the node environment, and hopefully knows what they are doing, by trying to do a node style require. If the code is called in a script tag, it will still be safe from any closures that may have been created elsewhere, and anything done in a separate closure is isolated from the rest of the code.

Update 2013-12-10:

After playing around with browserify and learning more about the node module system, I realized a mistake in this post. When a module is loaded in node, the this keyword doesn’t actually reference an empty object as I thought, it actually references exports. You could also check for a node environment with this === exports, but while this should work for the most part, it is still not as robust as the above test, as it would break if exports were defined as window, but the above can wistand this as exports would have to be defined in a closure to break the test.