Today, the Node.js module warehouse npmjs.com, has stored 150,000 modules, most of them are COMMONJS format.
The core of this format is the Require statement, which is loaded by the module. To learn node.js, you must learn how to use require statements. This article through the source Code analysis, detailed introduction require statement's internal operation mechanism, helps you to understand Node.js's module mechanism.
The basic usage of require ()
Before analyzing the source code, introduce the internal logic of the require statement first. If you only want to know the use of require, just look at this paragraph is enough.
The following content is translated from the Node usage guide.
When Node encounters require (X), it is processed in the following order.
(1) If X is a built-in module (e.g. require (' HTTP '))
A. Return the module.
B. No longer continues to execute.
(2) If X is "./" or "/" or ".. /"Start
A. Determine the absolute path of x according to the parent module where x is located.
B. Take X as a file, locate the following file in turn, and return the file as long as one of them exists, and no longer executes.
X
X.js
X.json
X.node
C. Take X as a table of contents, look for the following file, and if one exists, return the file and no longer execute.
X/package.json (main field)
X/index.js
X/index.json
X/index.node
(3) If X does not take the path
A. Determine the possible installation directory for x according to the parent module where x is located.
B. Sequentially in each directory, X is loaded as a filename or directory name.
(4) Throw "not Found"
Take a look at an example.
The current script file/home/ry/projects/foo.js executes require (' bar '), which falls into the third case above. The process of Node internal operation is as follows.
First, it is possible to determine the absolute path of x by following these locations, searching each directory in turn.
/home/ry/projects/node_modules/bar
/home/ry/node_modules/bar
/home/node_modules/bar
/node_modules/bar
When searching, Node first takes bar as the filename, and then tries to load the following files, as long as there is a successful return.
Bar
Bar.js
Bar.json
Bar.node
If none succeeds, the bar may be a directory name, and then try to load the following files in turn.
Bar/package.json (main field)
Bar/index.js
Bar/index.json
Bar/index.node
If you cannot find the bar's corresponding file or directory in all directories, you throw an error.
Second, the Module constructor
After understanding the internal logic, the following is a look at the source.
Require's source code is in Node's Lib/module.js file. For ease of understanding, the source code referenced in this article is simplified and the original author's comments are deleted.
function Module (ID, parent) {
This.id = ID;
This.exports = {};
This.parent = parent;
This.filename = null;
this.loaded = false;
This.children = [];
}
Module.exports = Module;
var module = new module (filename, parent);
In the code above, Node defines a constructor module, all of which are instances of module. As you can see, the current module (module.js) is also an instance of module.
Each instance has its own properties. Here's an example of what these properties are worth. Creates a new script file a.js.
A.js
Console.log (' module.id: ', module.id);
Console.log (' module.exports: ', module.exports);
Console.log (' module.parent: ', module.parent);
Console.log (' Module.filename: ', module.filename);
Console.log (' module.loaded: ', module.loaded);
Console.log (' Module.children: ', Module.children);
Console.log (' module.paths: ', module.paths);
Run this script.
$ node A.js
Module.id:.
Module.exports: {}
Module.parent:null
Module.filename:/home/ruanyf/tmp/a.js
Module.loaded:false
Module.children: []
Module.paths: ['/home/ruanyf/tmp/node_modules ',
'/home/ruanyf/node_modules ',
'/home/node_modules ',
'/node_modules ']
As you can see, if there is no parent module, call the current module directly, the Parent property is the Null,id property is a point. The FileName property is the absolute path to the module, and the Path property is an array that contains the possible location of the module. Also, the loaded property is False when the module is not fully loaded when the content is output.
Create another script file B.js to call A.js.
B.js
var a = require ('./a.js ');
Run B.js.
$ node B.js
Module.id:/home/ruanyf/tmp/a.js
Module.exports: {}
Module.parent: {Object}
Module.filename:/home/ruanyf/tmp/a.js
Module.loaded:false
Module.children: []
Module.paths: ['/home/ruanyf/tmp/node_modules ',
'/home/ruanyf/node_modules ',
'/home/node_modules ',
'/node_modules ']
In the code above, because A.js is called by B.js, the parent property points to the B.js module, and the id attribute is consistent with the FileName property, which is the absolute path of the module.
Three, the Require method of the module example
Each module instance has a require method.
Module.prototype.require = function (path) {
return Module._load (path, this);
};
Thus, require is not a global command, but an internal method provided by each module, that is, only within the module can the Require command be used (the only exception is the REPL environment). In addition, require actually calls the Module._load method internally.
below to see Module._load source code.
Module._load = function (request, parent, Ismain) {
Calculate absolute Path
var filename = Module._resolvefilename (request, parent);
First step: If there is a cache, take out the cache
var cachedmodule = Module._cache[filename];
if (cachedmodule) {
return cachedmodule.exports;
Step two: Whether it is a built-in module
if (nativemodule.exists (filename)) {
return nativemodule.require (filename);
}
Step three: Generate module instances and cache
var module = new module (filename, parent);
Module._cache[filename] = module;
Fourth Step: Load Module
try {
Module.load (filename);
Hadexception = false;
finally {
if (hadexception) {
Delete Module._cache[filename];
}
}
Fifth step: Exports property of output module
return module.exports;
};
In the code above, the absolute path (filename) of the module is first parsed to take it as the identifier for the module. Then, if the module is already in the cache, it is removed from the cache, and the module is loaded if it is not in the cache.
Therefore, the key step for Module._load is two.
Module._resolvefilename (): Determining the absolute path of a module
Module.load (): Loading module
Four, the absolute path of the module
The following is the source code for the Module._resolvefilename method.
Module._resolvefilename = function (Request, parent) {
The first step: if the built-in module, does not include the path to return
if (nativemodule.exists (request)) {
return request;
}
Step two: Identify all possible paths
var resolvedmodule = module._resolvelookuppaths (request, parent);
var id = resolvedmodule[0];
var paths = resolvedmodule[1];
Step three: Determine which path is true
var filename = Module._findpath (request, paths);
if (!filename) {
var err = new Error ("Cannot find module" + Request + "'");
Err.code = ' Module_not_found ';
throw err;
}
return filename;
};
In the code above, within the Module.resolvefilename method, two methods Module.resolvelookuppaths () and Module._findpath () are called, which lists the possible paths. The latter is used to confirm which path is true.
For simplicity's sake, only the results of module._resolvelookuppaths () are given here.
['/home/ruanyf/tmp/node_modules ',
'/home/ruanyf/node_modules ',
'/home/node_modules ',
'/node_modules '
'/home/ruanyf/.node_modules ',
'/home/ruanyf/.node_libraries ',
' $Prefix/lib/node ']
The array above is all possible paths for the module. Basically, the first level from the current path is looked up to node_modules subdirectories. The last three paths, mostly for historical reasons, remain compatible, and are actually rarely used.
With the possible path, the following is the source of Module._findpath (), which is used to determine which is the correct path.
Module._findpath = function (request, paths) {
List all possible suffix names:. Js,.json,. Node
var exts = Object.keys (module._extensions);
If it's an absolute path, no more searching.
if (Request.charat (0) = = '/') {
paths = ['];
}
Whether there is a suffix of the directory slash
var Trailingslash = (Request.slice (-1) = = '/');
First step: If the current path is already in the cache, it is returned directly to the cache
var CacheKey = json.stringify ({request:request, paths:paths});
if (Module._pathcache[cachekey]) {
return Module._pathcache[cachekey];
}
Step two: Iterate through all the paths sequentially
for (var i = 0, pl = paths.length i < pl; i++) {
var basepath = Path.resolve (paths[i], request);
var filename;
if (!trailingslash) {
Step three: Whether the module file exists
filename = Tryfile (basepath);
if (!filename &&!trailingslash) {
Fourth step: The module file plus the suffix name, whether there are
filename = tryextensions (BasePath, exts);
}
}
Fifth step: Is there a package.json in the directory
if (!filename) {
filename = Trypackage (BasePath, exts);
}
if (!filename) {
Sixth step: Whether there is a directory name + index + suffix name
filename = tryextensions (path.resolve (basepath, ' Index '), exts);
}
Step seventh: Save the found file path to return to the cache and return
if (filename) {
Module._pathcache[cachekey] = filename;
return filename;
}
}
Eighth step: No file found, return false
return false;
};
After the above code, you can find the absolute path of the module.
Sometimes in the project code, you need to call the absolute path of the module, so in addition to Module.filename, Node provides a require.resolve method for external calls to take from the module name to the absolute path.
Require.resolve = function (Request) {
return Module._resolvefilename (request, self);
};
Usage
Require.resolve (' A.js ')
Back to/home/ruanyf/tmp/a.js
Five, loading module
With the absolute path of the module, you can load the module. The following is the source code for the Module.load method.
Module.prototype.load = function (filename) {
var extension = path.extname (filename) | | '. js ';
if (! Module._extensions[extension]) Extension = '. js ';
Module._extensions[extension] (this, filename);
This.loaded = true;
};
In the code above, the suffix name of the module is first determined, and the different suffix names correspond to different loading methods. The following are the processing methods for the. js and. JSON suffix names.
module._extensions['. js '] = function (module, filename) {
var content = Fs.readfilesync (filename, ' utf8 ');
Module._compile (content), filename (Stripbom);
};
module._extensions['. JSON '] = function (module, filename) {
var content = Fs.readfilesync (filename, ' utf8 ');
try {
Module.exports = Json.parse (Stripbom (content));
} catch (Err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
Here we only discuss the loading of JS files. First, the module file is read as a string, then the UTF8 encoding-specific BOM file header is stripped, and the module is finally compiled.
The Module._compile method is used for module compilation.
Module.prototype._compile = function (content, filename) {
var self = this;
var args = [Self.exports, require, self, filename, dirname];
Return compiledwrapper.apply (Self.exports, args);
};
The code above is basically equivalent to the following form.
(function (exports, require, module, __filename, __dirname) {
Module source
});
In other words, the module is loaded in essence, inject exports, require, module three global variables, and then execute the module's source code, and then the module's exports variable value output.