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:
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:
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:
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.
Translations:
- Serbo-Croatian by Vera Djuraskovic (Webhostinggeeks.com)