In the Node.js module system, each file is treated as a separate module. For example, consider a file named foo.js:
const circle = require(‘./circle.js’);
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
On the first line, foo.js loads the module circle.js that is in the same directory as foo.js.
Here are the contents of circle.js:
const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;
The module circle.js has exported the functions area() and circumference(). Functions and objects are added to the root of a module by specifying additional properties on the special exports object.
Variables local to the module will be private, because the module is wrapped in a function by Node.js (see module wrapper). In this example, the variable PI is private to circle.js. The module.exports property can be assigned a new value (such as a function or object). Below, bar.js makes use of the square module, which exports a Square class:
const Square = require(‘./square.js’);
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`);
The square module is defined in square.js:
// Assigning to exports will not modify module, must use module.exports
module.exports = class Square {
constructor(width) {
this.width = width;
}
area() { return this.width ** 2; } };
The module system is implemented in the require(‘module’) module.
Accessing the main module
When a file is run directly from Node.js, require.main is set to its module. That means that it is possible to determine whether a file has been run directly by testing require.main === module. For a file foo.js, this will be true if run via node foo.js, but false if run by require(‘./foo’).
Because module provides a filename property (normally equivalent to __filename), the entry point of the current application can be obtained by checking require.main.filename.
Package Manager
The semantics of Node.js’s require() function were designed to be general enough to support a number of reasonable directory structures. Package manager programs such as dpkg, rpm, and npm will hopefully find it possible to build native packages from Node.js modules without modification.
Below we give a suggested directory structure that could work:
Let’s say that we wanted to have the folder at /usr/lib/node/<some-package>/<some-version> hold the contents of a specific version of a package.
Packages can depend on one another. In order to install package foo, it may be necessary to install a specific version of package bar. The bar package may itself have dependencies, and in some cases, these may even collide or form cyclic dependencies.
Since Node.js looks up the realpath of any modules it loads (that is, resolves symlinks), and then looks for their dependencies in the node_modules folders as described here, this situation is very simple to resolve with the following architecture:
- /usr/lib/node/foo/1.2.3/ – Contents of the foo package, version 1.2.3.
- /usr/lib/node/bar/4.3.2/ – Contents of the bar package that foo depends on.
- /usr/lib/node/foo/1.2.3/node_modules/bar – Symbolic link to /usr/lib/node/bar/4.3.2/.
- /usr/lib/node/bar/4.3.2/node_modules/* – Symbolic links to the packages that bar depends on.
Thus, even if a cycle is encountered, or if there are dependency conflicts, every module will be able to get a version of its dependency that it can use.
When the code in the foo package does require(‘bar’), it will get the version that is symlinked into /usr/lib/node/foo/1.2.3/node_modules/bar. Then, when the code in the bar package calls require(‘quux’), it’ll get the version that is symlinked into /usr/lib/node/bar/4.3.2/node_modules/quux.
Furthermore, to make the module lookup process even more optimal, rather than putting packages directly in /usr/lib/node, we could put them in /usr/lib/node_modules/<name>/<version>. Then Node.js will not bother looking for missing dependencies in /usr/node_modules or /node_modules.
In order to make modules available to the Node.js REPL, it might be useful to also add the /usr/lib/node_modules folder to the $NODE_PATH environment variable. Since the module lookups using node_modules folders are all relative, and based on the real path of the files making the calls to require(), the packages themselves can be anywhere.
To get the exact filename that will be loaded when require() is called, use the require.resolve() function. Putting together all of the above, here is the high-level algorithm in pseudocode of what require.resolve() does:
require(X) from module at path Y
- If X is a core module,
- return the core module
- STOP
- If X begins with ‘/’
- set Y to be the filesystem root
- If X begins with ‘./’ or ‘/’ or ‘../’
- LOAD_AS_FILE(Y + X)
- LOAD_AS_DIRECTORY(Y + X)
- LOAD_NODE_MODULES(X, dirname(Y))
- THROW “not found”
LOAD_AS_FILE(X)
- If X is a file, load X as JavaScript text. STOP
- If X.js is a file, load X.js as JavaScript text. STOP
- If X.json is a file, parse X.json to a JavaScript Object. STOP
- If X.node is a file, load X.node as binary addon. STOP
LOAD_INDEX(X)
- If X/index.js is a file, load X/index.js as JavaScript text. STOP
- If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
- If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
- If X/package.json is a file,
- Parse X/package.json, and look for “main” field.
- let M = X + (json main field)
- LOAD_AS_FILE(M)
- LOAD_INDEX(M)
- LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
- let DIRS = NODE_MODULES_PATHS(START)
- for each DIR in DIRS:
- LOAD_AS_FILE(DIR/X)
- LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
- let PARTS = path split(START)
- let I = count of PARTS – 1
- let DIRS = [GLOBAL_FOLDERS]
- while I >= 0,
- if PARTS[I] = “node_modules” CONTINUE
- DIR = path join(PARTS[0 .. I] + “node_modules”)
- DIRS = DIRS + DIR
- let I = I – 1
- return DIRS