Modules are the basic building blocks for constructing Node.js applications. A Node.js module encapsulates functions, hiding details inside a well-protected container, and exposing an explicitly-declared list of functions.
There are two module formats that we must consider:
- The traditional Node.js format based on the CommonJS standard has been used since Node.js was created.
- With ES2015/2016 a new format, ES6 Modules, has been defined with a new import keyword. ES6 modules will be (or is) supported in all JavaScript implementations.
Because ES6 modules are now the standard module format, the Node.js Technical Steering Committee (TSC) is committed to first-class support for ES6 modules.
We have already seen modules in action in the previous chapter. Every JavaScript file we use in Node.js is itself a module. It's time to see what they are and how they work. We'll start with CommonJS modules and then quickly bring in ES6 modules.
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 searches for the named module, loading 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 that would modify the object holding the module reference.
In every Node.js module, the exports object within the module is the interface exported to other code. Anything assigned to a field of the exports object is available to other pieces of code, and everything else is hidden. By the way, this object is actually module.exports. The exports object is an alias for module.exports.
The require function and module.exports objects both come from the CommonJS specification. ES6 modules have similar concepts, but a different implementation.
Let's look at a brief example of this before diving into the details. Ponder over the simple.js module:
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. Now, let's use it:
$ 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.
To reiterate the rule:
- Anything (functions or objects) assigned as a field of exports (as known as module.exports) is available to other code outside the module
- Objects not assigned to exports are not available to code outside the module, unless the module exports those objects via another mechanism
This is how Node.js solves the global object problem of browser-based JavaScript. The variables that look like they're global variables are only global to the module containing that variable. These variables are not visible to any other code.
Now that we've got a taste for modules, let's take a deeper look.