Module Development History

The concept of modules was not supported by browsers in the first place, so the namespace approach was proposed, but the big problem with this approach was that it was hard to guarantee that names would never repeat. In addition, many people use closures to implement the function, but because closures cause variables in the function to be stored in memory, improper use can cause performance problems.

Since then, Seajs, which implements the CMD specification, and RequireJS, which implements the AMD specification, have emerged. If you are interested, check them out, but they have been deprecated for now.

Since Node came along, it has implemented the CommonJS specification, which uses synchronous loading (also known as dynamic loading), but this specification is not available in browsers

Then came the UMD specification, which essentially unifies all three

Since ES6 came along, the ESModule specification has been developed, and attempts have been made to support the use of this specification in newer versions of Node, called MJS in Node

CommonJS profile

In fact, the principle of closure is used behind CommonJS to ensure the closed scope and encapsulation function. At the same time, it also solves the problem of dependency between modules. According to the CommonJS specification, a file is a module.

There are two common forms of modules. The first is system module, which is provided in Node, such as FS, HTTP, etc., and the second is file module, which is actually implemented by ourselves

The use of module files was to add module.exports to the module files we implemented

// school.js
module.exports = 'test'

// use school.js
let school = require('./school');
console.log(school)
Copy the code

CommonJS principle

Let’s implement a simple version of module loading ourselves to understand some of the main principles behind CommonJS

First, we define a basic framework and test code. Filename is a filename, and note that the filename may not have a suffix. When using a module in Node, if it is js or json, or the file ending in Node, the suffix can be omitted

function req(filename) {
}

let result = req('./school');
console.log(result);
Copy the code

Module. Exports (module. Exports). Node implements a cache mechanism to save performance when invoking modules. So the cache needs to be stored according to the absolute path. We can first define a Module constructor to create an instance of the Module, and then it will hold an exports property to store the data of the Module to be exported

functionModule(filename) {// constructor this.filename = filename; this.exports = {}; } Module._cache = {}; / / cacheCopy the code

We also need to define an array to hold various suffixes, which can be used to find and handle different file introductions

Module._extentions = ['.js'.'.json'.'.node'];
Copy the code

Next we define a function that resolves filename to an absolute path

let path = require('path');
let fs = require('fs');

Module._resolvePathname = function(filename) {
    let p = path.resolve(__dirname, filename);
    if(! path.extname(p)) {for(var i = 0; i < Module._extentions.length; i++){letnewPath = p + Module._extentions[i]; Try {// An exception occurs if the accessed file does not exist fs.accesssync (newPath);return newPath
            }catch(e){
            }
        }
    }
    returnp; // This is an absolute path.Copy the code

We then need to determine if the returned absolute path exists in the cache, and if so, return the cache’s exports property directly

functionFilename = module. _resolvePathname(filename) {// filename is a filename, the filename may have no suffix // we need to make an absolute path, the cache is based on the absolute path filename = module. _resolvePathname(filename); // Check if the path is in the cachelet cacheModule = Module._cache[filename];
    if(cacheModule) {// Exports are returned directly from the cachereturn cacheModule.exports
    }
}
Copy the code

If there is no cache, we need to start loading the module, first creating an instance of the module

let module = new Module(filename);
Copy the code

Then we need to define a load method to load the module, and we need to have different handling methods for different file types

let vm = require('vm');

Module.wrapper = [
    "(function(exports,require,module,__dirname,__filename){"."\n})"
]

Module.wrap = function(script) {
    return Module.wrapper[0] + script + Module.wrapper[1];
}

Module._extentions["js"] = function(module) { // {filename,exports={}}
    let script = fs.readFileSync(module.filename);
    let fnStr = Module.wrap(script);
    vm.runInThisContext(fnStr).call(module.exports, module.exports, req, module);
}

Module._extentions["json"] = function(module) {
    letscript = fs.readFileSync(module.filename); Module.exports = json.parse (script); // Exports = json.parse (script); // Exports = json.parse (script); } Module.prototype.load =function(filename) { //{filename:'c://xxxx',exports:'zfpx'} // The module can be JSON or JSlet ext = path.extname(filename).slice(1); // .js   .json
    Module._extentions[ext](this);
}
Copy the code

For JSON, we simply parsed the contents of the JSON file and returned it to the Module exports property. What we’re doing with JS is we’re concatenating the contents of the file into our closure function string and then we’re calling the closure execution with vm.runinthisContext (fnStr).call, and we’re passing the module object into the function, So when the contents of a module.exports file are executed, the exported data is passed to the module object’s exports property.

Module exports: Module exports: module exports: module exports: Module exports: Module exports

let path = require('path');
let fs = require('fs');
let vm = require('vm');

functionModule(filename) {// constructor this.filename = filename; this.exports = {}; this.loaded =true;
}

Module._extentions = ['.js'.'.json'.'.node']; Module._cache = {}; Module._resolvePathname =function(filename) {
    let p = path.resolve(__dirname, filename);
    if(! path.extname(p)) {for(var i = 0; i < Module._extentions.length; i++){letnewPath = p + Module._extentions[i]; Try {// An exception occurs if the accessed file does not exist fs.accesssync (newPath);return newPath
            }catch(e){}
        }
    }
    returnp; } module.wrapper = ["(function(exports,require,module,__dirname,__filename){"."\n})"
]

Module.wrap = function(script) {
    return Module.wrapper[0] + script + Module.wrapper[1];
}
Module._extentions["js"] = function(module) { // {filename,exports={}}
    let script = fs.readFileSync(module.filename);
    let fnStr = Module.wrap(script);
    vm.runInThisContext(fnStr).call(module.exports, module.exports, req, module);
}

Module._extentions["json"] = function(module) {
    letscript = fs.readFileSync(module.filename); Module.exports = json.parse (script); // Exports = json.parse (script); // Exports = json.parse (script); } Module.prototype.load =function(filename) { //{filename:'c://xxxx',exports:'zfpx'} // The module can be JSON or JSlet ext = path.extname(filename).slice(1); // .js   .json
    Module._extentions[ext](this);
}

functionFilename = module. _resolvePathname(filename) {// filename is a filename, the filename may have no suffix // we need to make an absolute path, the cache is based on the absolute path filename = module. _resolvePathname(filename); // Check if the path is in the cachelet cacheModule = Module._cache[filename];
    if(cacheModule) {// Exports are returned directly from the cachereturnCachemodule.exports} // No cache-loaded moduleletmodule = new Module(filename); // create module {filename:'Absolute path',exports:{}} module.load(filename); // load this module {filename:'xxx',exports = 'zfpx'}
    Module._cache[filename] = module;
    module.loaded = true; // Indicates whether the current module is finished loadingreturn module.exports;
}
let result = req('./school');
console.log(result);
Copy the code

The module loading strategy follows the logic of the following diagram

The rules for finding file modules can be seen in the following figure

  1. A./ cannot be added to the import of third-party modules
  2. CommonJS has issues with circular references, so be careful when introducing them
  3. Exports = ‘module. Exports’ =’ module. Exports’ = ‘module’ = ‘module.’ Exports in the Module object will not change