Introduction to Module in NodeJS study notes

  • 2021-08-05 08:50:14
  • OfStack

Node. js Module System

Node. js has a simple module loading system. In Node. js, files and modules correspond to 11 (each file is treated as a separate module).

For example, consider the following file named foo. js:


const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

In line 1, foo. js loads the module circle. js in the same directory as foo. js.

circle. js reads as follows:


const PI = Math.PI;

exports.area = (r) => PI * r * r;

exports.circumference = (r) => 2* PI * r;

Module circle. js derives the function area() And circumference() . To add functions and objects to the root directory of a module, you can assign them to a special exports object.

Variable 1 inside the module must be private because the module is wrapped in a function by Node. js (see module wrapper below). In this example, the variable PI is private to circle. js.

If you want the module to export a function (such as a constructor), or if you want to export a complete object instead of creating one property at a time, you need to assign it to module.exports Not exports.

In the following bar. js, the square Module, which exports a constructor:


const square = require('./square.js');
var mySquare = square(2);
console.log(`The area of my square is ${mySquare.area()}`);

Define an square method in the square. js module:


module.exports = (width) => {
  return {
    area: () => width * width;
  };
}

In addition, the module system is implemented in the require ("module") module.

"main" module

When an module runs directly from Node. js, it sets require. main to the module. You can use this to test whether the module is run directly or by require.

require.main === module

Take the file foo. js. If you run node, foo. js, this attribute is true. Running require ('./foo. js') is false.

Because module provides an filename (usually equivalent to __filename), you can check require. main. filename to get the entry point for the current application.

Some Tips for Package Manager

Node. js's require () function supports some reasonable directory structure. It allows package manager programs such as dpkg, rpm, and npm to build native packages directly from the Node. js modules without modification.

Below we give a suggested directory structure that works properly:

Suppose we want to do this in/usr/lib/node/ < some-package > / < some-version > To specify the version of the package.

In addition, packages can depend on each other. For example, if you want to install the foo package, this package may need to install the specified version of the bar package. bar packages are also likely to depend on other packages, and in some special cases, these dependent packages may even produce circular dependencies.

Because Node. js looks for realpath (that is, parsing soft chains) for all modules loaded, and then goes to the node_modules folder to look for dependent packages, this problem can be solved very simply with the following scenario:

/usr/lib/node/foo/1.2. 3/-Includes foo package, version 1.2. 3

/usr/lib/node/bar/4. 3.2/-Contains the bar package on which foo depends

/usr/lib/node/foo/1. 2.3/node_modules/bar-Softchain to/usr/lib/node/bar/4. 3.2/

/usr/lib/node/bar/4. 3.2/node_modules/*-Soft Chain Dependence to bar

Thus, each module can load and use the specified version of the package on which it depends, even if it encounters circular dependencies or dependency conflicts.

When require ('bar') is in the foo package, it can be softly linked to the specified release/usr/lib/node/foo/1. 2.3/node_modules/bar. Then, when the code in the bar package calls require ('quux'), it can also be soft-linked to the specified version of /usr/lib/node/bar/4. 3.2/node_modules/quux.

The whole process of module loading (key, the pseudo-code flow 1 written below must be remembered)

To get the exact file name that will be loaded when calling require (), use the require. resolve () function.

The following is the whole process of module loading and the parsing process of require. resolve:


//  Loading X Module 
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 './' or '/' or '../'
  a. LOAD_AS_FILE(Y + X)
  b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"

//  Loading X Documents 
//  Loading procedure: X -> X.js -> X.json -> X.node
LOAD_AS_FILE(X)
1. If [X] is a file, load [X] as JavaScript text. STOP
2. If [X.js] is a file, load [X.js] as JavaScript text. STOP
3. If [X.json] is a file, load [X.json] as JavaScript text. STOP
4. If [X.node] is a file, load [X.node] as JavaScript text. STOP

//  Load entry file 
//  Loading procedure: X -> X/index.js -> X/index.json -> X/index.node
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, load [X/index.json] as JavaScript text. STOP
3. If [X/index.node] if a file, load [X/index.node] as JavaScript text. STOP

//  Load Folder 
LOAD_AS_DIRECTORY(X)
1. If [X/package.json] is a file.
  a. Parse [X/package.json], and look for "main" field
  b. let M = X + (json main field)
  c. LOAD_AS_FILE(M)
  d. LOAD_INDEX(M)
2. LOAD_INDEX(X)
 
//  Loading node Module 
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS;
  a. LOAD_AS_FILE(DIR/X)
  b. LOAD_AS_DIRECTORY(DIR/X)

//  List all possible node_modules Path 
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START);
2. let I = count of PARTS - 1
3. let DIRS = []
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

Module cache

All modules are cached after the first load. This means that you get exactly the same object every time you call require ('foo').

Multiple calls to require ('foo') may not execute the module's code multiple times. This is an important function. Using it, you can return the "partially done" object, allowing modules to be loaded layer by layer based on dependencies, even though doing so may result in circular dependencies.

If you want a module to execute code every time it is loaded, you need exports 1 function and call it.

Module cache considerations

Modules are cached based on their parsed file names. Depending on the path of the calling module, the called module may resolve a different file name (loaded from the node_modules folder). It does not guarantee that require ('foo') will always return the same object every time it parses a different file.

Also, on case-insensitive file systems or operating systems, different parsed file names can point to the same file, but the cache still treats them as different modules and reloads the file multiple times. For example, require ('./foo') and require ('./FOO') return two different objects regardless of whether./foo and./FOO are the same file.

Core module

Node. js Some modules are compiled into binary files. These modules are described in more detail in other parts of this document.

The core module is in the source code lib/folder of Node. js.

If the module identities of core modules are passed to require (), they are always loaded first. For example, even if there is a custom module called http, if we execute require ('http'), it will always return the built-in HTTP module.

Circular reference

When require () is referenced cyclically, the return module may not be completed.

Consider this situation:

a. js:


console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b. js:


console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

app. js:


console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);

When app. js loads a. js, a. js loads b. js in turn. At this point, b. js attempts to load a. js. To prevent an infinite loop, an incomplete copy of the a. js exported object is returned to the b. js module. b. js then completes loading and provides its exported object to the a. js module.

When app. js loads both modules, they are complete. Therefore, the output of the program will be:


$ node app.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
in main, a.done =true, b.done = true

Module wrapper

Before executing the module's code, Node. js uses a function wrapper to wrap the module's contents, as follows:


(function (exports, require, module, __filename, __dirname) {
  //  Your module code 
});

By doing so, Node. js achieves the following:

It limits the scope of top-level variables (defined as var, const, or let) inside the module to be inside the module rather than global.

It helps to provide a global variable inside the module that actually belongs only to the module, such as:

The module and exports objects are used to help derive 1 value from within the module

The variables __filename and __dirname are the file name and folder path finally resolved by the current module

module Object Signature


const PI = Math.PI;

exports.area = (r) => PI * r * r;

exports.circumference = (r) => 2* PI * r;

0

require Function Signature


const PI = Math.PI;

exports.area = (r) => PI * r * r;

exports.circumference = (r) => 2* PI * r;

1


Related articles: