Chapter 3. Node.js Modules

Before writing Node.js applications, you must learn about Node.js modules and packages. Modules and packages are the building blocks for breaking down your application into smaller pieces.

In this chapter, we will cover the following topics:

  • Defining a module
  • The CommonJS module specification
  • Using ES-2015 modules in Node.js
  • Understanding how Node.js finds modules
  • The npm package management system

So, let's get on with it.

Defining a module

Modules are the basic building block 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.

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.

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:

var fs = require('fs');

The require function searches for modules, loading the module definition into the Node.js runtime, and makes its functions available. In this case, the fs object contains the code (and data) exported by the fs module.

This is true of every Node.js module, the exports object within the module is the interface provided to other code. Anything assigned to a field of the exports object is available to other pieces of code, and everything else is hidden.

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++; }

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.

Defining a module

The exports object is what's 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 is available to other code outside the module
  • Objects not assigned to exports are not available

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.

Node.js module format

Node.js's module implementation is strongly inspired by, but not identical to, the CommonJS module specification. The differences between them might only be important if you need to share code between Node and other CommonJS systems.

One change that came with ES2015 is a standard module format. It has some interesting features, but it is incompatible with the CommonJS/Node.js module system. Further, the Node.js runtime (as of versions 5.x and 6.x) does not support ES2015 modules.

Babel supports transpiling ES2015 modules to Node.js modules. This suggests a possible strategy for code to be shared between Node.js and browser applications. Namely, to write ES2015 modules, transpiling them as needed to either Node.js or straight to JavaScript for browsers that don't support ES2015 modules.

In the long run, the Node.js community will face a choice once compatibility with ES2015 modules comes about. It will be extremely important for the require function to support both CommonJS style Node.js modules and ES2015 modules. The specifics for doing so are under discussion.

In the meantime, we need to focus on the task at hand, which is to learn about Node.js modules.

File modules

The simple.js module we just saw is what the Node.js documentation describes as a file module. Such modules are contained within a single file, whose filename ends with either .js or .node. The latter are compiled from C or C++ source code, while the former are of course written in JavaScript.

Node.js modules provide a simple encapsulation mechanism to hide implementation details while exposing an API. Modules are treated as if they were written as follows:

(function() { … contents of module file … })();

Thus, everything within the module is contained within an anonymous private namespace context. This is how the global object problem is resolved; everything in a module which looks global is actually contained within this private context.

There are two free variables inserted by Node.js into this private context: module and exports. The module object contains several fields you might find useful. Refer to the online Node.js documentation for details.

The exports object is an alias of the module.exports field. This means that the following two lines of code are equivalent:

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

Your code can break the alias between the two if you do this:

exports = function(arg, arg1) { … }

If you want the module to return a single function, you must instead do this:

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

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

Demonstrating module-level encapsulation

That was a lot of words, so let's do a quick example. Create a file named module1.js containing this:

var A = "value A";
var B = "value B";
exports.values = function() {
  return { A: A, B: B };
}

Then create a file named module2.js containing the following:

var util = require('util');
var A = "a different value A";
var B = "a different value B";
var m1 = require('./module1');
util.log('A='+A+' B='+B+' values='+util.inspect(m1.values()));

Then, run it as follows (you must have Node.js already installed):

$ node module2.js
19 May 21:36:30 - A=a different value A B=a different value B values={ A: 'value A', B: 'value B' }

This artificial example demonstrates encapsulation of the values in module1.js from those in module2.js. The A and B values in module1.js don't overwrite A and B in module2.js because they're encapsulated within module1.js. Values encapsulated within a module can be exported, such as the .values function in module1.js.

Directories as modules

A module can contain a whole directory structure full of stuff. Such a module might contain several internal modules, data files, template files, documentation, tests, assets, and more. Once stored within a properly constructed directory structure, Node.js will treat these as a module that satisfies a require('moduleName') call.

Note

This may be a little confusing because the word module is being overloaded with two meanings. In some cases, a module is a file, and in other cases, a module is a directory containing one or more file modules.

One way to implement this is with a package.json file. As the name implies, it is in the JSON format, and it contains data about the module (package). The npm package management system puts a lot of data into the package.json file, but the Node.js runtime recognizes only these two values:

{ name: "myAwesomeLibrary",
   main: "./lib/awesome.js" }

If this package.json file is in a directory named awesomelib, then require('./awesomelib') will load the file module in ./awesomelib/lib/awesome.js.

If there is no package.json, then Node.js will look for either index.js or index.node. In this case, require('./awesomelib') will load the file module in ./awesomelib/index.js.

In either case, the directory module can easily contain other file modules. The module that's initially loaded would simply use require('./anotherModule') one or more times to load other, private modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset