An overview of the

Node.js is an implementation based on the CommonJS module specification. Each file is a module and has its own scope. The code within the module must be written in accordance with the CommonJS specification. Variables, functions, and classes defined in files are private and invisible to other files.

If you want to share variables across multiple files, you must define them as properties of the Global object.

global.x = 5;
Copy the code

This allows the variable x to be read by all files. However, this is not recommended.

CommonJS module features:

  • All code runs in module scope and does not pollute globally;

  • The module can be loaded many times, but only run once at the first loading. The running result is cached, and the cached result is read directly after loading. Run again, the cache must be cleared;

  • The order in which modules are loaded, in the order in which they appear in the code.

The module object

Node provides a Module builder, of which all modules are instances.

function Module(id, parent) {
	this.id = id;
    this.exports = {};
    this.parent = parent;
    // ...
}
Copy the code

The CommonJS specification states that each module has a Module object inside it that represents the current module. Its exports attribute is the interface to the outside world. Loads a module and loads its module.exports property.

var x = 5;
var add = function (val) {
	return val + x;
};
module.exports.x = x;
module.exports.add = add;
Copy the code

The above code outputs the x variable and the function add via module.exports.

The require method is used to load modules.

var example = require('./example.js');

console.log(example.x);
console.log(example.add);
Copy the code

Module object properties:

Module. id The module’s identifier, usually the module’s file name with an absolute path;

Module. filename specifies the filename of the module with an absolute path.

Module. loaded Returns a Boolean value indicating whether the module has completed loading.

Module. parent returns an object representing the module that called the module;

Module. children returns an array representing other modules used by this module;

Module. exports indicates the value that the module exports.

When a module is invoked on the command line, such as Node something.js, module.parent is null. If called in a script, such as require(‘./ something-js ‘), module.parent is the module that calls it. Using this, you can determine whether the current module is an entry script.

if(!module.parent) {
	app.listen(8088.function() {
    	console.log('app listening on port 8088'); })}else {
	module.exports = app;
}
Copy the code

The module exports attributes

The module.exports property represents the interface that the current module exports to other files that load the module and actually read the module.exports variable.

var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();

setTimeout(function() {
	module.exports.emit('ready');
}, 1000);
Copy the code

The above module will issue the ready event 1 second after loading. Other files listen for this event as follows:

var a = require('./a');
a.on('ready'.function() {
	console.log('module a is ready');
});
Copy the code

Exports variable

Node provides an exports variable for each module, pointing to module.exports. Add a line to the header of each module:

var exports = module.exports;
Copy the code

When exporting module interfaces, you can add methods to exports objects:

exports.area = function (r) {
	return Math.PI * r * r;
};
Copy the code

Exports cannot be assigned a value directly, which breaks the link between exports and module.exports. Such as:

exports = function (x) {console.log(x)};
Copy the code

Exports no longer refers to module.exports.

If a module’s external interface is a single value, it cannot be exported using exports, only module.exports can be used.

module.exports = 'Hello world';
Copy the code

If you can’t handle their differences, you can just use module.exports.

Compatibility between AMD specification and CommonJS specification

The CommonJS specification loads modules synchronously, and only after the loading is complete can the following operations be performed. The AMD specification is asynchronously loaded modules, allowing you to specify callback functions. Node.js is mainly used for server programming, module files generally exist in the local hard disk, so the load is relatively fast, do not consider asynchronous loading mode, more suitable for CommonJS specification. Browser environment, need to load modules from the server side, it must use asynchronous mode, so the browser side generally uses AMD specifications.

The AMD specification defines modules using the define method:

define(['package/lib'].function(lib){
    function foo(){
        lib.log('hello world! ');
    }
    
    return {
        foo: foo
    };
});
Copy the code

The AMD specification allows the output of modules compatible with the CommonJS specification:

define(function(require.exports.module){
    var someModule = require("someModule");
    var anotherModule = require("anotherModule");
    
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
    
    exports.asplode = function (){
        someModule.doTehAwesome();
        anotherModule.doMoarAwesome();
    };
});
Copy the code

The require command

Node’s built-in require command is used to load module files.

The basic function of the require command, which reads and executes a JS file and then returns the exports object of that module. An error is reported if no specified module is found.

// example.js
var invisible = function() {
    console.log("invisible");
}

exports.message = "hi";

exports.say = function() {
    console.log(message);
}
Copy the code

Exports objects:

var example = require('./example.js');
example

/ / output
/ / {
// message: "hi";
// say: [Function]
// }
Copy the code

If a module outputs a function, it cannot be defined in an exports object, it is defined in module.exports.

module.exports = function() {
    console.log("hello world")}require('./example2/js') ()Copy the code

The require command calls itself, which is equivalent to executing module.exports.

Load the rules

The require command is used to load a file. The default suffix is.js.

The require command looks for module files in different paths, depending on the format of the argument.

(1) If the argument starts with a “/”, it indicates that an absolute path is loaded.

(2) If the argument starts with “./”, it indicates that a relative path is loaded.

(3) If the parameter does not start with either of the preceding two parameters, it indicates that a core module provided by default (in Node’s system installation directory) is loaded, or an installed module (globally or locally installed) is loaded in node_modules.

(4) If the argument does not start with “./” or “/”, and it is a path, such as require(‘example/path/file’), find the location of example, and then use it as an argument to find the path.

(5) If the specified module file is not found, Node will try to add.js,.json, and.node to the file name before searching. .js files are parsed as javascript files in text format,.json files are parsed as jSON-formatted text files, and.node files are parsed as compiled binary files.

(6) If you want to get the exact filename of the require command load, use require.resolve().

For example:

Execute require(‘bar.js’), and Node will search the files in turn:

/usr/local/lib/node/bar.js

/home/user/projects/node_modules/bar.js

/home/user/node_modules/bar.js

/home/node_modules/bar.js

/node_modules/bar.js

Loading rules for directories

Usually, we keep related files in the same directory for easy organization. It is best to set up an entry file for this directory. The require method can use this entry file to load the entire directory.

Place a package.json file in your directory and write the entry file to the main field.

{
    "name": "some-library"."main": "./lib/some-library.js"
}
Copy the code

When require discovers that the parameter is a directory, it automatically looks at the package.json file in that directory and loads the entry file specified by the main field. If package, JSON file has no main field or package.json file, load the index.js or index.node file in the directory.

Module cache

Node caches a module the first time it is loaded. Load the module later and fetch its module.exports property directly from the cache.

If you want to execute a module more than once, you can have the module output a function, and each time require the module, re-execute the output function.

All cached modules are stored in require.cache.

// Delete the cache for the specified module
delete require.cache[moduleName];

// Delete the cache for all modules
Object.keys(require.cache).forEach(function(key) {
    delete require.cache[key];
})
Copy the code

Note that the cache identifies modules by absolute path. If the same module name is stored in a different path, the require command will still reload the module.

Environment variable NODE_PATH

When Node executes a script, it first looks at environment variables, which are a set of absolute paths separated by colons. When the specified module can’t be found anywhere else, Node looks in these paths.

NODE_PATH can be added to.bashrc.

export NODE_PATH="/usr/local/lib/node"
Copy the code

If you encounter complex relative paths such as: “.. /.. /.. /.. /lib/myModule”, you can add the file to node_modules, you can also modify the NODE_PATH environment variable, package.json file can be written as follows:

{
    "name": "node_path"."version": "1.0.0"."description": ""."main": "index,js"."scripts": {
        "start": "NODE_PATH=lib node index.js"
    },
    "author": ""
}
Copy the code

The NODE_PATH environment variable is a legacy path solution and should not normally be used, instead the node_modules directory should be used.

Cyclic loading of modules

Cyclic loading of modules, i.e. A loads B and B loads A, B will load an incomplete version of A.

// a.js
exports.x = 'a1';
console.log('a.js'.require('./b.js').x);
exports.x = 'a2';

// b.js
exports.x = 'b1';
console.log('b.js'.require('./a.js').x);
exports.x = 'b2';

// main.js
console.log('main.js'.require('./a.js').x);
console.log('main.js'.require('./b.js').x);
Copy the code

Js loaded b.js, and b.js loaded A.js. Node returns an incomplete version of a.js, so the console.log output in main.js is different from the console.log output in main.js.

$ node main.js
b.js a1
a.js b2
main.js a2
main.js b2
Copy the code

First execution. The main js the require (‘. / a. s.) x, the execution of a. s first, and then load the b.j s, performing b.j s, a. s. exports. X is equal to the a1, b.j s completes, so exports. X is equal to the b2, The second execution of console.log(‘main.js’, require(‘./b.js’).x) reads the exports property directly from the cache, so the console.log statements inside main.js b2, A.js and b.js are not executed.

require.amin

The require method has a main attribute that determines whether the module is executed directly or if it is called to execute (by loading the script with require). When executed directly (node module.js), the require.main property refers to the module itself require.main === module and results in true.

Module loading mechanism

The loading mechanism for CommonJS modules is to output a value that is not affected by changes within the module.

// lib.js
var counter = 3;
function incCounter() {
    counter++;
}
module.exports = {
    counter: counter,
    incCounter: incCounter
};
Copy the code
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter); / / 3
incCounter();
console.log(counter); / / 3
Copy the code

The above code shows that after the output of counter, the changes inside the module do not affect counter.

Require’s internal process

The require command is not a global command, but refers to the module.require command of the current module, which calls Node’s internal command module._load.

Module._load = function(request, parent, isMain) {
  // 1. Check whether Module._cache exists in the cache
  // 2. Create a new Module instance if it is not in the cache
  // 3. Save it to cache
  // 4. Use module.load() to load the specified module file. After reading the file contents, use module.pile () to execute the file code
  // 5. If an error occurs during load/parse, the module is removed from the cache
  // exports. Returns the module.exports of this module
};
Copy the code

In step 4, execute the script for the specified module using module.compile(). The logic is as follows:

Module.prototype._compile = function(content, filename) {
  // 1. Generate a require function pointing to module.require
  // 2. Load other helper methods to require
  // 3. Put the file contents into a function that calls require
  // 4. Execute the function
};
Copy the code

The require function and its auxiliary methods are as follows:

Require (): Loads external modules

Require.resolve () : Resolves the module name to an absolute path

Require. main: points to the main module

Require. cache: a module that points to all caches

Require. extensions: Calls different execution functions depending on the file name extension

Once the require function is ready, all the script content to be loaded is put into a new function to avoid polluting the global environment. Arguments to this function include require, Module, exports, and others.

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

The module. _compile method is executed synchronously, so module. _load waits for it to complete before returning the value of module.exports to the user.

Refer to the link

This paper refers to ruan Yifeng’s article (CommonJS specification), and makes some changes and summaries. For study and easy reference only.