The module

Unlike other high-level languages, Java has class files, Python has import mechanisms… Javascript can only introduce code through script tags, which is messy and has naming conflicts, and we have to be very careful and limited in our development process.

Commonjs

  • The original

    Commonjs wants Javascript to run anywhere.

    In the course of development, Javascript mainly shines in the browser, but for the back end, Javascript still has many disadvantages and defects:

    • No modular system
    • No standard interface
    • There are fewer standard libraries, and common requirements such as file operations and IO streams are not available
    • The lack of a package management system makes it impossible to do dependent installations and build applications quickly

    Commonjs expects Javascript to be able to write these applications that run on various host platforms:

    • Command line tool
    • Server application
    • Desktop graphical interface applications
    • Hybrid applications (Adobe AIR, etc.)

Node uses Commonjs Modules specification to implement a very easy to use module system. NPM’s support for Packages allows developers to get more out of the way.

Commonjs module specification

Three cores:

  • The module reference

    The corresponding module API is introduced via the require() method, which receives a module identifier

  • The module definition

    The current context provides an exports object that exports the methods and variables of the current module, which is the only export of the module, and a Module object that represents the module itself, which is an exports object that is a module property. Node, where a file is a module, can export a method by mounting it directly on an Exports object

    // a.js
    exports.sayHi = function() {
        console.log('hello');
    }
    // b.js
    const a = require('a');
    exports.sayHello = a.sayHi;
    Copy the code
  • Module identifier

    The argument passed to the require method must conform to the small camel name string, or the path string, without the file suffix js at the end.

Significance of modules:

The clustering methods and variables are limited in the private scope, providing import and export functions, and smoothly connecting the context. Each module is independent and does not interfere with each other.

Module mechanism of Node

Node doesn’t follow the Commonjs specification exactly, it adds the features it needs.

There are three processes for importing a module in Node:

  • 1. Path analysis
  • 2. Locate files
  • 3. Compile and execute

Module classification:

  • Core modules: provided internally by Node;

    During the compilation of Node source code, these modules are compiled into binary executable files. When the Node process starts, these modules are loaded into memory. This part of the module is only loaded in the path analysis phase, which is the fastest loading process.

  • File module: developed by the user;

    Dynamic loading, all three processes go through, slightly slower.

Preferentially load from cache

For the second import of the same module, Node will find the imported module from the cache to save the overhead of the second import. The actual cache is the compiled and executed object.

Fetching from the cache is the first priority when loading modules, whether core or file.

Path analysis and file location

There are several forms of identifiers, and the search and location of modules vary to different degrees with different identifiers.


Module identifier analysis

  • Core modules, HTTP, FS, PATH, etc.

    Second in priority to caching, trying to load a custom file module with the same identifier as the core module will not succeed.

  • Relative path of the file module;

    The require method converts the passed path to the real path, uses the real path as an index, and stores the result of compilation and execution in the cache for next loading. Because the file path is specified, it is second only to the core module in loading speed.

  • A file module with an absolute path starting with /;

    Same as above

  • Non-path file module, self-defined and core module name does not conflict module;

    Slowest loading speed, a particular file module, maybe a package or a file,


    — Module path —

    Node’s search strategy for file modules is represented by an array of paths.

    If we create a js file with console.log(module.paths) and execute it, in Windows we should print something like this: [‘D:\\workspace\ node_modules’, ‘D:\ node_modules’]

    So, we can probably get the generation rule of the module path:

    Start with node_modules in the current directory and work your way up to node_modules in the root directory. This is also the way prototype chains work in Javascript. It’s easy to see that the deeper the path, the longer it takes.


File location

  • File name extension analysis

    As mentioned above, module identifiers can be searched without file suffixes, in which case the search order is.js,.json,.node. During the search process, the FS module needs to be called to synchronously block to determine whether the file exists, so this may cause performance problems. When importing JSON or Node files, we try to add suffixes as much as possible to speed up the process.

  • Directory analysis and packages

    If a file is not found and a directory is found, Node treats the directory as a package. This procedure supports the Commonjs package specification:

    • Find package.json (commonJS package description file) in the current directory. Use json. parse to parse the package description objectmainProperty specified

    File name to locate the fault. If the suffix name is missing, the extension analysis phase is entered again.

    • ifmainProperty specified incorrectly, or if the property is not present/package.json is not present, Node will use theindexAs filename for extension analysis lookup.
    • If no corresponding file is found in directory analysis, go to the next fileThe module pathRepeat the above actions.
    • Module path array traversal completed, not found, an error is reported.

Module compilation Each file module is an object

function Module(id, parent) {   
   this.id = id;   
   this.exports = {};   
   this.parent = parent;   
   if (parent && parent.children) {
       parent.children.push(this);   
   } 
   this.filename = null;   
   this.loaded = false;   
   this.children = []; 
} 
Copy the code

Compile and execute are the final steps in importing a module. After locating a specific file, Node creates a new object, which is loaded and compiled according to the path, distinguished by the file extension:

  • .js file, read and compile synchronously through fs module;
  • .node file, this isExtension files written in C/C++Through thedlopenMethod loads the file generated by the last compilation;
  • Json file, with fs module synchronization after reading the file, withJSON.parse()Parse the return result;
  • The rest of the extension files are treated as JS files

Each time a Module is successfully compiled, its Module path is cached as a key in the module. cache object.

Depending on the file extension, Node calls a different method to read, such as a. Json file

Module.extensions['.json'] = function (module, fileName) { const content = NativeModule.require('fs').readFileSync(fileName, 'utf-8'); try { module.exports = JSON.parse(stripBOM(content)); } catch (e) { throw e; }}Copy the code

The module. extensions will be assigned to the require.extensions property.

After determining the extension of the file, Node calls the specific compilation method to execute the file before returning the result to the caller.

  • Compilation of Javascript files

    Commonjs: require, module, exports, __filename, __dirname exist in each module file, but we did not declare them.

    During compilation, Node wraps the entire Javascript file as follows:

    (function (exports, require, module, __filename, __dirname) {   
        var math = require('math');   
        exports.area = function (radius) {     
            return Math.PI * radius * radius;   
        }; 
    });
    Copy the code

    Each file is scoped out, and the wrapped code is executed through the runInThisContext method of the VM native object (similar to eval, but with no global contamination), returning a function object. This function is then passed the exports, require, module of the current module and the file path and directory retrieved from the file location as arguments.

Why don’t you justexports = xxx, butmodule.exports = xxx?

Because exports are passed in as parameters, direct assignment changes the form to change the reference to the parameter. Variables outside the domain cannot be modified.

  • C/C++ module compilation

Node calls the process.dlopen method to load and execute. The.node file does not need to be compiled, and the module’s exports object is returned to the caller during execution.

  • .json file compilation

Fs module was used to read file contents synchronously, which was parsed by json. parse and assigned to exports object.

The core module

Core modules are divided into:

  • C/C++ modules: stored in the SRC directory of the Node project
  • Javascript modules: stored in the lib directory
Compilation of Javascript core modules

All Javascript modules are compiled into C/C++ code before all C/C++ modules are compiled, but they are not executable files.

  • Dump to C/C++ code

    Node_natives. H file is generated by converting all the built-in Javascript codes SRC /node.js and lib/*.js into C++ arrays.

    The Javascript code is stored in the Node namespace as a string and cannot be executed. After the Node process starts, it is loaded into the memory.

  • Compile Javascript core modules

    Like the file module, the core module also goes through a header and tail wrapper, executes and exports exports objects, but the difference is how the source file is retrieved and where the execution result is cached.

    The source file is retrieved by process.binding(‘natives’) and cached in the NativeModule._cache object, while the file Module is stored in the module. _cache object.

Built-in module

Core parts written in C/C++ and wrapped objects completed in Javascript are the main way Node improves performance. Modules written in pure C/C++ are called built-in modules and are almost never called by users. Fs Buffer and other modules are not built in.

AMD

Node’s module introductions are almost always synchronous (calling the FS module to synchronize the loading file), and if you use this approach in a front-end system, it can block UI rendering. So another module specification emerged, AMD, asynchronous module definition.

  • Definition module

    define(id? , dependencies? The factory); The factory is essentially the module code content. Specific examples:

    define(function() {
        var exports = {};
        exports.sayHi = function() {
            alert('hi');
        };
        return exports;
    });
    Copy the code

    Unlike inside Node, you need to explicitly define a module (again to close the scope), and you need to return exported objects.

CMD

This specification is closer to Commonjs. Different from AMD, CMD supports dependency on dynamic import. It passes the three variables require, exports and module into define method as parameters for module definition.

define(function(require, exports, module) {
    // do something
});
Copy the code

AMD must introduce all dependencies when defining modules, whether they are used or not, CMD can be flexibly introduced.

Conclusion:

  • In order to limit the methods and variables of the clustering in the private scope, provide the introduction and export functions, and smoothly connect the context, each module is independent and does not interfere with each other. Nodejs module functionality is present;
  • Following the Commonjs specification, a module has three main variablesExports, require, Module;
  • Three cores of module: module reference, module definition and module identification;
  • The process introduced by the module:
    • Path analysis (Module identifier Analysis)
      • Core module priority
      • Relative and absolute paths are next
      • Custom modules last
    • File location
      • Extension analysis, in the order.js->.json->.node
      • Directories and packages, look for package.json files
    • Compile implementation
      • Create a new object
      • Loading the file and executing will return the result based onThe file pathcached
      • Returns the result of execution to the caller
  • Modules are divided into core modules and file modules (written by the developers themselves). The loading mechanism is different, resulting in different loading speeds. Cache loading is the first priority.
  • The compilation of Javascript files goes through a head-to-tail wrapping process, as wellModule, require, exports, __filename, __dirnameThe origin of five variables.
  • The Commonjs method of loading modules synchronously only works in Node environments. In browser environments, modules should be loaded asynchronously, hence AMD.
  • While AMD needs to pass in all dependencies as parameters when defining a module, CMD can introduce dependencies dynamically.