Node exports & require

In my post about Node modules I demonstrate the usage of require() and exports without going into detail. Structuring a Node deployment can be tricky, mostly because there’s more than one way to organize modules in the file system (particularly when working with npm, the de-facto package management tool for Node). Knowing how require() and exports work is important, especially if you’re planning to develop a complex Node module:

The require() function

When developing Node applications, we get to work with two kinds of modules: the ones that are part of our application (simple or folder modules) and external modules that serve a general purpose. We use the require() function for accessing both by specifying relative path and module name for local modules and name only for external modules. The following piece of code (taken from connect, a popular Node library) demonstrates both ways:

This code is part of connect.js (see the image on the right) which is one module out of several that make the connect framework. On the first line, the EventEmitter, which is a part of the events module (a standard module that is built into Node) is imported by specifying the module name only. The same happens for the path and fs modules on lines 4 and 5. On the other hand, the proto and utils local modules are imported (lines 2 and 3) by specifying the relative path and name (If, for example, the proto module was located one folder up then the relative path and name would be ‘../proto’).

It’s obvious how Node finds the local modules, but what about the external ones? When the name of the modules that is passed to the require() function doesn’t include a path, Node will first search for a built in module with the same name (this is the reason why 3rd party libraries can’t override built in ones) and if it doesn’t find one it starts the following lookup procedure:

  1. Check under the ../node_modules folder.
  2. Check under ../../node_modules, ../../../node_modules, etc. until the root directory is reached.
  3. If the NODE_PATH env var is set to a list of absolute paths (colon delimited), then node will search them as well.

Here’s the actual implementation of the require() function (taken from the file node.js):

Before moving on to discussing the export object, there is one last important thing to cover. Lines 6-10 and line 21 deal with modules cache. Node saves loaded modules in a cache and returns them on subsequent calls to require() that ask for the same modules. You can make use of this fact to perform module initialization operations, but you need to be careful – cached module objects are mapped by their absolute file names. This means that modules can be loaded more than once! To understand how this might happen take a look at the following example project structure:

According to the rules of external modules loading that I described earlier, external_module that was installed under both my_proj/node_modules and my_proj/core/node_modules will be loaded twice if used by both module_a and module_b. This isn’t, actually, necessarily a bad thing because it gives you the opportunity to use two different versions of the same library in different parts of your module (for example, you can use an unstable version of some module to experiment new features and have only a selected part of you application depending on it while the rest of the application keeps using stable versions).

Line 29 returns the ‘required’ module’s exports object as the result. Let’s see what this means:

The exports & module.exports objects

If you looked at code examples of Node projects, you might have noticed that modules writers export their API using exports or module.exports:

Most of the times lines 1 & 2 have the same effect, but not always. Surprisingly, many Node developers can’t tell the difference… The following code snippet that is taken from node.js explains it:

Modules code is wrapped by Node to create a private context. The wrapping function is passed five arguments, two of which are exports and module (line 6). Line 15 is where the module is created and, as you can see, the value that is passed as exports is the module.exports object (this.exports). So if nothing’s changed, inside a module exports just is a reference to module.exports. If you go back to the require() function implementation above, you’ll see that it always returns module.exports. So where’s the problem?

Let’s say that we want to write a module that represents a class:

To get this done you need to have something similar to that:

Notice how the module.exports reference was changed? This means that all previous assignments to exports or module.exports aren’t accessible through the module.exports reference anymore (the one that is returned by require())! The exports reference is still available but points to completely different object (probably the one that was initially assigned by Node). I don’t know what’s the reason for passing both exports and module.exports but I generally recommend using the module.exports (you probably understand why).

%d bloggers like this: