Node.js Modules

Amol Gunjal
4 min readMay 9, 2020

In the Node.js module system, each file is treated as a separate module

consider a file named shape.js

shape.js
const circle = require(‘./circle.js’);
console.log(`The area of a circle of radius 5 is ${circle.area(5)}`);

On the first line, shape.js loads the module circle.js that is in the same directory as shape.js

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(). In this example, the variable PI is private to circle.js.
Variables local to the module will be private because the module is wrapped in a function by Node.js (see module wrapper)

The module.exports property can be assigned a new value (such as a function or object).

The module system is implemented in the require(‘module’) module.

The module wrapper

Before a module’s code is executed, Node.js will wrap it with a function wrapper that looks like the following:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});

By doing this, Node.js achieves a few things:

  • It keeps top-level variables (defined with var, const or let) scoped to the module rather than the global object.
  • It helps to provide some global-looking variables that are actually specific to the module, such as:
    — The module and exports object that the implementor can use to export values from the module.
    — The convenience variables __filename and __dirname, containing the module’s absolute filename and directory path.

Accessing the main module

When a file is run directly from Node.js, require.main is set to its module.

For a file shape.js, this will be true if run via node shape.js, but false if run by require(‘./shape’).

Because the module provides a filename property (normally equivalent to __filename), the entry point of the current application can be obtained by checking to require.main.filename.

The high-level algorithm in pseudocode

Here is the high-level algorithm in pseudocode of what require() does:

require(X) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with '/'
a. set Y to be the filesystem root
3. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
c. THROW "not found"
4. LOAD_SELF_REFERENCE(X, dirname(Y))
5. LOAD_NODE_MODULES(X, dirname(Y))
6. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as its file extension format. STOP
2. If X.js is a file, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP

LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon. STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. If "main" is a falsy value, GOTO 2.
c. let M = X + (json main field)
d. LOAD_AS_FILE(M)
e. LOAD_INDEX(M)
f. LOAD_INDEX(X) DEPRECATED
g. THROW "not found"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_PACKAGE_EXPORTS(DIR, X)
b. LOAD_AS_FILE(DIR/X)
c. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = [GLOBAL_FOLDERS]
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIRS + DIR
d. let I = I - 1
5. return DIRS

LOAD_SELF_REFERENCE(X, START)
1. Find the closest package scope to START.
2. If no scope was found, return.
3. If the `package.json` has no "exports", return.
4. If the name in `package.json` isn't a prefix of X, throw "not found".
5. Otherwise, load the remainder of X relative to this package as if it
was loaded via `LOAD_NODE_MODULES` with a name in `package.json`.

LOAD_PACKAGE_EXPORTS(DIR, X)
1. Try to interpret X as a combination of name and subpath where the name
may have a @scope/ prefix and the subpath begins with a slash (`/`).
2. If X does not match this pattern or DIR/name/package.json is not a file,
return.
3. Parse DIR/name/package.json, and look for "exports" field.
4. If "exports" is null or undefined, return.
5. If "exports" is an object with some keys starting with "." and some keys
not starting with ".", throw "invalid config".
6. If "exports" is a string, or object with no keys starting with ".", treat
it as having that value as its "." object property.
7. If subpath is "." and "exports" does not have a "." entry, return.
8. Find the longest key in "exports" that the subpath starts with.
9. If no such key can be found, throw "not found".
10. let RESOLVED =
fileURLToPath(PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name),
exports[key], subpath.slice(key.length), ["node", "require"])), as defined
in the ESM resolver.
11. If key ends with "/":
a. LOAD_AS_FILE(RESOLVED)
b. LOAD_AS_DIRECTORY(RESOLVED)
12. Otherwise
a. If RESOLVED is a file, load it as its file extension format. STOP
13. Throw "not found"

module.builtinModules

const builtin = require(‘module’).builtinModules
> console.log( builtin );
[ ‘async_hooks’,
‘assert’,
‘buffer’,
‘child_process’,
‘console’,
‘constants’,
‘crypto’,
‘cluster’,
‘dgram’,
‘dns’,
‘domain’,
‘events’,
‘fs’,
‘http’,
‘http2’,
‘_http_agent’,
‘_http_client’,
‘_http_common’,
‘_http_incoming’,
‘_http_outgoing’,
‘_http_server’,
‘https’,
‘inspector’,
‘module’,
‘net’,
‘os’,
‘path’,
‘perf_hooks’,
‘process’,
‘punycode’,
‘querystring’,
‘readline’,
‘repl’,
‘stream’,
‘_stream_readable’,
‘_stream_writable’,
‘_stream_duplex’,
‘_stream_transform’,
‘_stream_passthrough’,
‘_stream_wrap’,
‘string_decoder’,
‘sys’,
‘timers’,
‘tls’,
‘_tls_common’,
‘_tls_wrap’,
‘trace_events’,
‘tty’,
‘url’,
‘util’,
‘v8’,
‘vm’,
‘zlib’,
‘v8/tools/splaytree’,
‘v8/tools/codemap’,
‘v8/tools/consarray’,
‘v8/tools/csvparser’,
‘v8/tools/profile’,
‘v8/tools/profile_view’,
‘v8/tools/logreader’,
‘v8/tools/arguments’,
‘v8/tools/tickprocessor’,
‘v8/tools/SourceMap’,
‘v8/tools/tickprocessor-driver’,
‘node-inspect/lib/_inspect’,
‘node-inspect/lib/internal/inspect_client’,
‘node-inspect/lib/internal/inspect_repl’

--

--