How require loads modules in Node.js

May 27th, 2013 at 9:22 am

One of the useful tools Node.js adds on top of standard ECMAScript is a notation for defining and using modules. A "module" exports objects and functions by adding them to exports, and another module can import it by using require. The semantics are explained well in the official documentation.

While the documentation does a good job describing how require finds the module to import, it doesn’t say much about how the importing itself happens, and how the exports and module objects are magically visible and usable in the module’s code. Here I want to provide a lower-level view of this missing link, gleaned from the source code of Node.js v0.10.8 (lib/module.js).

As the documentation linked above explains, there are a few places modules can be found in by require. There’s also a number of different imports require can perform – from folders, from JSON files, from compiled Node modules (C++), and so on. Here I’ll focus on importing from a regular JavaScript source file (.js).

Code from .js files is simply read into a string. Next, the code string is wrapped with a function:

(function (exports, require, module, __filename, __dirname) {
   // <-- MODULE CONTENTS HERE -->
});

These wrapped contents are evaluated by the JavaScript VM, and the result is a function object; let’s call it module_func. This function is invoked as follows:

module_func.apply(module.exports,
                  [module.exports, require, module, filename, dirname]);

And the return value of require is then module.exports.

What’s module? It’s the special module object the require mechanism has built for loading our module. It’s actually described quite well in the modules documentation I mentioned before. That page says:

In each module, the module free variable is a reference to the object representing the current module. In particular module.exports is accessible via the exports module-global. module isn’t actually a global but rather local to each module.

Now it should be obvious how this comes to be. The wrapper function created for our code has the arguments module and exports, which become visible in the code. The apply invocation above shows what they get bound to. What it also does is set this in the global scope of our code to the exports property of the module. So the following are all equivalent ways to add stuff to a module’s exports:

exports.say_hi = function () {console.log('hi');}
module.exports.say_bye = function () {console.log('bye');}
this.say_farewell = function () {console.log('farewell');}

The last way look suspicious. We know that by default, variables in the code don’t get exported. In other words, in:

var foo = 1;
exports.bar = 2;

While bar is exported to require, foo is not. But how can this be, if this is bound to exports? Doesn’t var foo = 1 add foo to this, being the global object?

This is only puzzling if you think of your code as stand-alone JavaScript, in which the global scope is truly global. But recall, from a few paragraphs above, that our code is wrapped in a function. So the "global" scope in the module is actually function scope. In function scope, variables don’t get auto-bound to this. Mystery solved.

One final note: Node.js has a few useful flags you can set as environment variables for debugging. In particular, I’ve found setting:

$ NODE_DEBUG=module node <file.js>

Very handy for following through the module loading process require does.

Related posts:

  1. Creating Python extension modules in C
  2. Compiling C DLLs and using them from Perl

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)