Since its inception, no one has ever regarded Javascript as a programming language. In the Web 1.0 era, this scripting language was mainly used for form verification and web page special effects. It was not until the Web 2.0 era that front-end engineers used it to greatly improve the user experience on the web page that JS was widely valued. As JS becomes increasingly popular, it roughly undergoes changes in tool library, component library, front-end framework, and front-end applications. Javascript is inherently lacking a feature: modules, and the emergence of the CommonJS specification makes up for this shortcoming. This article will introduce the CommonJS specification and Node module mechanism.
In other high-level languages, Java has class files, Python has import mechanism, and PHP has include and require. The way JS introduces code through the <script> tag seems messy. In the past, people had to use namespaces and other methods to artificially constrain code. It was not until the CommonJS specification emerged that front-end and back-end Javascript was able to achieve great unity. Node borrows CommonJS's Modules specification to implement a very easy-to-use module system.
1. CommonJS module specification
CommonJS module specification is divided into 3 parts:
1). Module reference: introduce a module's API into the current context through the require() method and passing in a module identifier, such as var math = require('math');
2).Module definition: Export the methods or variables of the current module through the exports object. There is also a module object in the module, and exports are actually the attribute of the module. In Node, a file is a module, and the "global variables" in the module are invisible to the outside world. Only the attributes mounted on exports are public, such as exports.add = function() {}; exports.PI = 3.1415926;
3).Module ID: It is actually the parameter passed to require(). For example, the 'math' mentioned above, it must be a string that conforms to the camel nomenclature, or a relative or absolute path starting with "." ".." It can have no file name suffix ".js"
2. Node module implementation process
In Node, modules are divided into two categories: one is the core module provided by Node itself, and the other is the file module written by the user himself. Some of the core modules are compiled into binary files during the compilation of Node source code. When Node starts, the core module is directly loaded into memory, so its loading speed is the fastest. The file module is loaded dynamically at runtime and requires three steps: path analysis, file location, and compilation and execution. Note that Node caches introduced modules to reduce the overhead during secondary introduction, and adopts the most preferred strategy for secondary loading of the same module.
2.1 Path Analysis
Path analysis mainly analyzes the module identifiers mentioned above, which are mainly divided into the following categories:
1) Core modules, such as http, fs, path, etc.
2) Relative path file module starting with . or .
3) Absolute path file module starting with /
4) Custom file module, which may be in the form of a file or package. Node will try to find the target file one by one according to the module path array module.paths, usually along the current directory step by step until the root directory, looking for a directory named node_modules, so this is the most time-consuming way to find it.
2.2 File location
Based on path analysis, the following details need to be paid attention to:
1) File extension analysis: Since the CommonJS specification allows module identifiers not to be filled in, Node will try in order of .js, .json, and .node
2) Directory analysis and package: If no corresponding file is found after the above file extension analysis, but a directory is obtained, Node will treat the directory as a package.
2.3 Compilation and execution
After locate the specific file, Node will create a new module object, load and compile according to the path. For different extensions, the loading method is different:
1) .js file: read the file synchronously through the fs module and compile and execute it
2) .node file: This is an extension file written in C/C++, loaded through the dlopen() method
3) .json file: read the file synchronously through the fs module, and use JSON.parse() to parse and return the result
4) Other extension files: they are loaded as .js files
We know that each module file has three variables: require, exports, and module by default. Even in the Node API document, we know that each module also has two variables: filename and dirname. Where do they come from? How does the Node module achieve that the declared "global variables" will not actually pollute other modules? In fact, Node will wrap the file contents in the beginning and end during compiling the JS module. Here is an example of a JS file being wrapped by head and tail:
The code copy is as follows:
(function(exports, require, module, __filename, __dirname) {
/* The middle is the actual content of the JS file*/
var math = require('math');
exports.area = function(radius) {
return Math.PI * radius * radius;
};
/* The actual content of the JS file ends*/
});
In this way, each module file is scoped isolation, and variables such as require, exports, modules, etc. are also injected into the module context. This is the implementation of Node's CommonJS module specification. The compilation process of C/C++ module and Node core module is relatively complicated, so I will not repeat it again.
3. Module call stack
It is necessary to clarify the calling relationships of various modules in Node, as shown in the figure below:
The built-in module of C/C++ is the lowest-level module and belongs to the core module. It mainly provides APIs to call Javascript core modules and third-party Javascript file modules. In fact, there is almost no contact with such modules. There are two main responsibilities of Javascript core modules: one is to serve as the encapsulation layer and the bridging layer of the built-in module of C/C++ for file module calls, and the other is a pure functional module that does not need to deal with the underlying layer. File modules are usually written by third parties, including ordinary Javascript modules and C/C++ extension modules.
4. Package and NPM
4.1 Package Structure
The package is essentially an archive file (usually .zip or .tar.gz), which is decompressed and restored to a directory after installation. CommonJS package specification consists of two parts: package structure and package description file. A package structure that fully complies with the CommonJS specification should contain the following files:
1).package.json: package description file
2).bin: The directory where executable binary files are stored
3).lib: The directory where Javascript code is stored
4).doc: The directory where the document is stored
5).test: The directory where unit test cases are stored
4.2 Package Description File
The package description file is a JSON file - package.json, located in the root directory of the package, is an important part of the package and is used to describe the overview information of the package. All behaviors of NPMs that will be mentioned later are closely related to the fields of this file. Below, we will use the package.json file of a well-known Web framework express project as an example to illustrate the meaning of some commonly used fields.
1).name: Package name
2).description: Package introduction
3).version: Version number must comply with "semantic version control", refer to http://semver.org/
4).dependencies: Use the list of packages that the current package needs to depend on. This property is very important. NPM will automatically load the dependent package through this property
5).repositories: List of locations for hosting source code
The usage of other fields can be referred to the NPM package.json description
4.3 Common functions of NPM
NPM (node package manager), commonly known as node package manager. Its main function is to manage node packages, including: installation, uninstall, update, view, search, publish, etc.
4.3.1 NPM package installation
There are two types of installation of Node packages: local installation and global installation. The difference between the two is as follows:
1). Local installation npm install <package-name>: package will be downloaded to the current directory and can only be used in the current directory.
2). Global installation npm install -g <package-name>: package will be downloaded to a specific system directory, and the installed package can be used in all directories.
4.3.2 NPM package management
The following is a list of commonly used package management commands using grunt-cli (grunt command line tool) as an example:
1).npm install: Install all packages declared by the dependencies and devDependencies fields of the package.json file
2).npm install [email protected]: Install specific version of grunt-cli
3).npm install grunt-contrib-copy --save: install grunt-contrib-copy and save the dependency to the package.json file
4).npm uninstall grunt-cli: uninstall package
5).npm list: Check which packages are installed
6).npm publish <folder>: Publish package