CommonJS overview

CommonJS is a modular standard, and NodeJS is an implementation of this standard. Each file is a module with its own scope. Variables, functions, and classes defined in one file are private and invisible to other files.

Simple implementation of NodeJS modularization

Before implementing module loading, we need to clear the module loading process:

  • Assuming thatAThere’s one under the foldera.js, we want to solve for an absolute path;
  • The path we write may not have a suffix.js,.json;
  • Get a real load path (the module will be cached) first check the cache to see if the file exists, if it does not return the cache to create a module;
  • Get the contents of the corresponding file, add a closure, insert the contents, and execute.

1. Load the modules needed in advance

Since we are only implementing CommonJS module loading methods, we are not implementing the entire Node, we need to rely on some Node modules, so we are “shameless” to use Node’s require method to load modules in.

Rely on the module
1
2
3
4
5
6
7
8
Copy the code
Const fs = require(const fs = require("fs"); Const path = require(const path = require("path"); Const vm = require(const vm = require(const vm = require("vm");
Copy the code

2. Create the Module constructor

For every Module introduced in CommonJS, we need to create an instance through the Module constructor.

Create the Module constructor
1, 2, 3, 4, 5, 6, 7, 8Copy the code
/*
* @param {String} p
*/
function Module(p) {
    this.id = p; // Representation of the current file (absolute path)
    this.exports = {}; // Each module has an exports property that stores the contents of the module
    this.loaded = false; // Whether the flag has been loaded
}
Copy the code

Define static properties to store some of the values we need to use

Module static variable
12 3 4 5 6 7 8 9 10 11 12 13 14Copy the code
// The string of closures to use after the function
Module.wrapper = [
    "(function (exports, require, module, __dirname, __filename) {"."\n})"
];

// The object of the module cached according to the absolute path
Module._cacheModule = {};

// How to handle different file name extensions
Module._extensions = {
    ".js": function() {},
    ".json": function() {}};Copy the code

4. Create a reQ method for importing modules

To prevent it from having the same name as Node’s native require method, we renamed the simulated method to req.

Introduce the module method req
1, 2, 3, 4, 5, 6, 7, 8, 9, 10Copy the code
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // Process the argument passed by req as an absolute path
    let p = Module._resolveFileName(moduleId);

    // Generate a new module
    let module = new Module(p);
}
Copy the code

In the above code, we first pass an absolute path through module. _resolveFileName, and create a Module instance that passes the absolute path as an argument. We now implement the module. _resolveFileName method.

5, return the implementation of the file absolute path module. _resolveFileName method

If the req parameter does not have an extension name, the file will be searched in the order of the extension name in the Module._extensions key until the absolute path to the file is found. Js first, then.json, and here we only implement the processing of those two file types.

Handles the absolute path _resolveFileName method
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33Copy the code
/*
* @param {String} moduleId
*/
Module._resolveFileName = function(moduleId) {
    // Concatenate the parameters into absolute paths
    let p = path.resolve(moduleId);

    // Check whether there is a suffix
    if (!/\.\w+$/.test(p)) {
        // Create an array.js.json that specifies the order in which file extensions are found
        let arr = Object.keys(Module._extensions);

        // loop search
        for (let i = 0; i < arr.length; i++) {
            // Concatenate the absolute path with the suffix name
            let file = p + arr[i];
            // An exception was caught when the file could not be found
            try {
                // Search for the changed path through the fs module synchronous file search method. If the file is not found, the catch statement will be directly entered
                fs.accessSync(file);

                // If the file is found, the absolute path of the file is returned
                return file;
            } catch (e) {
                // An exception is thrown when no corresponding file is found
                if (i >= arr.length) throw new Error("not found module"); }}}else {
        // The absolute path with a suffix is returned directly
        returnp; }};Copy the code

6. Load method for loading modules

Improve the REQ method
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21Copy the code
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // Process the argument passed by req as an absolute path
    let p = Module._resolveFileName(moduleId);

    // Generate a new module
    let module = new Module(p);

    // ********** here is the new code **********
    // Load the module
    let content = module.load(p);

    // Assigns the loaded content returned to the module instance's exports property
    module.exports = content;

    // Finally returns the module instance's exports property, which loads the contents of the module
    return module.exports;
    // ********** is the new code **********
}
Copy the code

This code implements an instance method called load, which passes in the absolute path to the file, loads the contents of the file for the module, stores the value in the exports property of the module instance, and returns the contents.

The load method
1
2
3
4
5
6
7
8
9
10
11
Copy the code
// Module loading method
Module.prototype.load = function(filepath) {
    // What is the suffix of the loaded file
    let ext = path.extname(filepath);

    // Process file contents according to different suffixes. The argument is the current instance
    let content = Moudule._extensions[ext](this);

    // Return the processed result
    return content;
};
Copy the code

7, the implementation of loading.js file and.json file method

Remember that the module. _extensions static property is used to store these two methods, so let’s refine them.

The _Extensions object that handles the suffix name method
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24Copy the code
Module._extensions = {
    ".js": function(module) {
        // Return the contents of the file
        let script = fs.readFileSync(module.id, "utf8");

        // Add a closure to the contents of the js file
        let fn = Module.wrap(script);

        // Create the virtual machine, execute the js function we created, and point this to the exports property of the module instance
        vm.runInThisContext(fn).call(
            module.exports,
            module.exports,
            req,
            module
        );

        // Returns the exports property on the module instance (that is, the contents of the module)
        return module.exports;
    },
    ".json": function(module) {
        //. Json file processing is relatively simple, just read the string into an object
        return JSON.parse(fs.readFileSync(module.id, "utf8")); }};Copy the code

We’re using the module. wrap method, which looks like this and actually helps us add a closure environment (that is, a layer of functions and the parameters we need) in which all the variables are private.

Create the closure wrap method
1
2
3
Copy the code
Module.wrap = function(content) {
    return Module.wrapper[0] + content + Module.wrapper[1];
};
Copy the code

The two values of module.wrapper are the first and last parts of a function that we need to wrap around.

Here we highlight, very important: 1. We built the closure in the virtual machine and used the above/below call to point this to the exports property of the module instance, so that’s why we launched a JS file with Node and printed this instead of the global object. It is an empty object, which is our module.exports, the exports property of the current module instance. Exports = module. Exports = XXX; exports = module. Exports = XXX; exports = module. That is, the contents of the module are stored directly on the module instance’s exports property, and reQ returns the contents of our module’s exports. 3. The third argument is passed to REq because it is possible to import other modules in a module, and reQ will return the exports of other modules to be used in the current module, thus setting up the whole CommonJS rule.

8. Cache the loaded modules

Our current program is a problem, when a module has been loaded repeatedly, when executing the reQ method will find, and create a new module instance, this is not reasonable, so let’s implement the cache mechanism.

Remember the static property module. _cacheModule, whose value is an empty object into which we store all loaded Module instances.

Improving reQ methods (handling caching)
12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35Copy the code
/*
* @param {String} moduleId
*/
function req(moduleId) {
    // Process the argument passed by req as an absolute path
    let p = Module._resolveFileName(moduleId);

    // ********** here is the new code **********
    // Check whether it has been loaded
    if (Module._cacheModule[p]) {
        // The module exists and returns the exports object directly
        return Module._cacheModule[p].exprots;
    }
    // ********** is the new code **********

    // Generate a new module
    let module = new Module(p);

    // Load the module
    let content = module.load(p);

    // ********** here is the new code **********
    // The absolute path of the module is used as the key corresponding to the contents of the module
    Module._cacheModule[p] = module;

    // Cache indicates true
    module.loaded = true;
    // ********** is the new code **********

    // Assigns the loaded content returned to the module instance's exports property
    module.exports = content;

    // Finally returns the module instance's exports property, which loads the contents of the module
    return module.exports;
}
Copy the code

9. Try reQ loading module

Create a new file a.js under the same directory, export anything using module.exports, and try importing and printing at the bottom of our module load.

Export a custom module
1
2
Copy the code
// a.js
module.exports = "Hello world";
Copy the code
Req detection method
1
2
Copy the code
const a = req("./a");
console.log(a); // Hello world
Copy the code

CommonJS module lookup specification

In fact, we only implemented a part of CommonJS specification, namely the loading of custom modules. In fact, there are many rules about module search in CommonJS specification, and we use the following flow chart to express the details.




In this article, we learn about CommonJS. The main purpose of this article is to understand the implementation idea of CommonJS module. If you want to know more about the implementation details of CommonJS, we suggest that you take a look at the corresponding part of NodeJS source code. You can also use VSCode to introduce modules by calling the require method and debug them step by step in the Node source code.

The original source: https://www.pandashen.com