Before the CommonJs specification was proposed, Javascript did not have a module system, which meant that it was difficult for us to develop large-scale applications because the organization of the code would be more difficult.
First of all, CommonJS is not unique to Node. CommonJS is a module specification that defines how to reference and export modules. Nodejs only implements this specification. The CommonJS module specification is mainly divided into three parts: module reference, module definition and module identification.
Module reference
Module reference means that we can introduce other modules through require
.
const { add } = require('./add'); const result = add(1,2);
Module defines
a file as a module, and the module will provide two variables, namely module and exports. module is the current module itself, exports is the content to be exported, and exports is an attribute of the module, that is, exports is module.exports. The content imported by other modules through require is the content of module.exports.
// add.js exports.add = (a, b) => { return a + b; }
Module identification
The module identification is the content in require. For example, require('./add')
, then the module identification is ./add
.
The module import and export mechanism built through CommonJS allows users to easily build large-scale applications without having to consider variable pollution.
Node module implementation
Node implements the CommonJs specification and adds some features it needs. Node mainly does the following three things to implement the CommonJs specification:
Path analysis
file positioning
, compilation and execution of
path analysis.
When require() is executed, the parameter received by require is the module identifier, and node performs path analysis through the module identifier. The purpose of path analysis is to find the path where this module is located through the module identifier. First of all, node modules are divided into two categories, namely core modules and file modules. The core module is the module that comes with node, and the file module is the module written by the user. At the same time, file modules are divided into file modules in the form of relative paths, file modules in the form of absolute paths, and file modules in the form of non-paths (such as express).
When node finds a file module, it will compile, execute and cache the module. The general principle is to use the full path of the module as the key and the compiled content as the value. This will not be needed when the module is introduced for the second time. Then perform path analysis, file location, compilation and execution of these steps. The compiled content can be read directly from the cache.
//Cache module diagram: const cachedModule = { '/Usr/file/src/add.js': 'Compiled content of add.js', 'http': 'Compiled content of the http module that comes with Node', 'express': 'Compiled content of non-path custom file module express' // ... }
When you want to find the module imported by require, the order of searching for the module is to first check whether the module is already in the cache. If it is not in the cache, then check the core module, and then look for the file module. Among them, file modules in the form of paths are easier to find. The complete file path can be obtained based on the relative or absolute path. It is relatively troublesome to find custom file modules in non-path form. Node will search for the file from the node_modules folder.
Where is the node_modules directory? For example, the file we are currently executing is /Usr/file/index.js;
/** * /Usr/file/index.js; */ const { add } = require('add'); const result = add(1, 2);
In this module, we have introduced an add module. This add is not a core module nor a file module in the form of a path. So how to find the add module at this time.
Module has a path attribute. The path to find the add module is in the paths attribute. We can type this attribute to take a look:
/** * /Usr/file/index.js; */ console.log(module.paths);
We can print out the value of paths by executing node index.js in the file directory. The value in paths is an array, as follows:
[ '/Usr/file/node_modules', '/Usr/node_modules', '/node_modules', ]
That is, Node will sequentially search from the above directory to see if it contains the add module. The principle is similar to the prototype chain. First, start searching in the node_modules folder in the directory of the same level as the currently executed file. If the node_modules directory is not found or does not exist, continue searching to the upper level.
File location
path analysis and file location are used together. The file identifier can be without a suffix, or a directory or a package may be found through path analysis. In this case, locating the specific file requires some additional processing. .
File extension analysis
const { add } = require('./add');
For example, in the above code, the file identifier does not have an extension. At this time, node will search for the existence of .js, .json, and .node in sequence. document.
Directory and package analysis
are the same as the above code. What is found through ./add
may not be a file, but may be a directory or package (judge whether it is a directory or a package by judging whether there is a package.json file in the add folder). At this time, the steps for file positioning are as follows:
If there is no main field in package.json, index will also be used as a file, and then extension analysis will be performed to find the file with the corresponding suffix.
Module compilation
The main modules we encounter in development are json modules and js modules.
json module compilation
When we require a json module, Node will actually help us use fs.readFilcSync to read the corresponding json file, get the json string, and then call JSON.parse to parse to get the json object, and then assign it to the module. exports, and then give it to require.
js module compilation
When we require a js module, such as
// index.js const { add } = require('./add');
// add.js exports.add = (a, b) => { return a + b; }
What happened at this time? Why can we use the variables module, exports, and require directly in the module. This is because Node wraps the contents of the module first and last when compiling the js module.
For example, the add.js module will be packaged into a structure similar to this when actually compiled:
(function(require, exports, module) { exports.add = (a, b) => { return a + b; } return module.exports; })(require, module.exports, module)
That is, the js file we write will be packaged into a function. What we write is only the content in this function, and the subsequent packaging process of Node is hidden from us. This function supports passing in some parameters, including require, exports and module.
After the js file is compiled, the file will be executed. Node will pass the corresponding parameters to this function and then execute it, and return the module.exports value to the require function.
The above is the basic process for Node to implement CommonJs specifications.