Examining the traditional Node.js module format

We already saw CommonJS modules in action in the previous chapter. It’s now time to see what they are and how they work.

In the ls.js example in Chapter 2, Setting Up Node.js, we wrote the following code to pull in the fs module, giving us access to its functions:

const fs = require(‘fs’);

The require function is given a module identifier, and it searches for the module named by that identifier. If found, it loads the module definition into the Node.js runtime and making its functions available. In this case, the fs object contains the code (and data) exported by the fs module. The fs module is part of the Node.js core and provides filesystem functions.

By declaring fs as const, we have a little bit of assurance against making coding mistakes. We could mistakenly assign a value to fs, and then the program would fail, but as a const we know the reference to the fs module will not be changed.

The file, ls.js, is itself a module because every source file we use on Node.js is a module. In this case, it does not export anything but is instead a script that consumes other modules.

What does it mean to say the fs object contains the code exported by the fs module? In a CommonJS module, there is an object, module, provided by Node.js, with which the module’s author describes the module. Within this object is a field, module.exports, containing the functions and data exported by the module. The return value of the require function is the object. The object is the interface provided by the module to other modules. Anything added to the module.exports object is available to other pieces of code, and everything else is hidden. As a convenience, the module.exports object is also available as exports.

Because exports is an alias of module.exports, the following two lines of code are equivalent:

exports.funcName = function(arg, arg1) { … };

module.exports.funcName = function(arg, arg2) { .. };

Whether you use module.exports or exports is up to you. However, do not ever do anything like the following:

exports = function(arg, arg1) { … };

Any assignment to exports will break the alias, and it will no longer be equivalent to module.exports. Assignments to exports.something are okay, but assigning to exports will cause failure. If your intent is to assign a single object or function to be returned by require, do this instead:

module.exports = function(arg, arg1) { … };

Some modules do export a single function because that’s how the module author envisioned delivering the desired functionality.

When we said ls.js does not export anything, we meant that ls.js did not assign anything to module.exports.

To give us a brief example, let’s create a simple module, named simple.js:

var count = 0;

exports.next = function() { return ++count; };

exports.hello = function() {

return “Hello, world!”;

};

We have one variable, count, which is not attached to the exports object, and a function, next, which is attached. Because count is not attached to exports, it is private to the module.

Any module can have private implementation details that are not exported and are therefore not available to any other code.

Now, let’s use the module we just wrote:

 $ node
> const s = require(‘./simple’);
undefined
> s.hello();
‘Hello, world!’
> s.next();
1
> s.next();
2
> s.next();
3

> console.log(s.count);
undefined
undefined
>

The exports object in the module is the object that is returned by require(‘./simple’). Therefore, each call to s.next calls the next function in simple.js. Each returns (and increments) the value of the local variable, count. An attempt to access the private field, count, shows it’s unavailable from outside the module.

This is how Node.js solves the global object problem of browser-based JavaScript. The variables that look like they are global variables are only global to the module containing the variable. These variables are not visible to any other code.

The module object is a global-to-the-module object injected by Node.js. It also injects two other variables:  dirname and   filename. These are useful for helping code in a module know where it is located in the filesystem. Primarily, this is used for loading other files using a path relative to the module’s location.

For example, one can store assets like CSS or image files in a directory relative to the module. An app framework can then make the files available via an HTTP server. In Express, we do so with this code snippet:

app.use(‘/assets/vendor/jquery’, express.static(

path.join( dirname, ‘node_modules’, ‘jquery’)));

This says that HTTP requests on the /assets/vendor/jquery URL are to be handled by the static handler in Express, from the contents of a directory relative to the directory containing the module. Don’t worry about the details because we’ll discuss this more carefully in a later chapter. Just notice that  dirname is useful to calculate a filename relative to the location of the module source code.

To see it in action, create a file named dirname.js containing the following:

console.log(‘dirname: ${ dirname}‘);

console.log(‘filename: ${ filename}’);

This lets us see the values we receive:

$ node dirname.js

dirname: /home/david/Chapter03

filename: /home/david/Chapter03/dirname.js

Simple enough, but as we’ll see later these values are not directly available in ES6 modules.

Now that we’ve got a taste for CommonJS modules, let’s take a look at ES2015 modules.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *