In the CommonJS specification, a file can be treated as an independent module with its own scope. Variables, functions, etc. defined within the file belong to the module only and are not visible to other modules. If you want other modules to be able to use their internal variables, you need to export them using module.exports and then import them using require() in other modules.

Because Node is a concrete implementation of CommonJS specification, we mainly use the CommonJS module of Node to explain the modularity thinking under CommonJS specification.


I. Analysis of common problems

The CommonJS module mechanism for Node will be resolved step by step with the following questions:

1. What is a CommonJS module in Node and what information does a module contain? Exports and module.exports. 3. How to determine whether the current module is a root module or a submodule? 4. When the same module is introduced several times, will the code inside the module be executed multiple times? 5. Does an infinite loop occur when two modules reference each other repeatedly? 6. Is the introduction of modules synchronous or asynchronous? 7. Which file types are supported by require by default? Exports, require, module, __filename, __dirname, exports, require, module, __filename, __dirname 9. Why are variables inside a module invisible to other modules? 10. The id passed in when require(id) is called.Copy the code

1. CommonJS Module

Node has a constructor: Module. Each Module we use is actually an instance of Module generated by the constructor new, which is represented by the variable Module. The source code for the Module constructor is as follows:

function Module(id = "", parent) {
  // id is usually passed the absolute path to the file
  this.id = id;
  this.path = path.dirname(id);
  this.exports = {};
  // The updateChildren function adds the created module instance to the children list of the parent module
  updateChildren(parent, this.false);
  this.filename = null;
  this.loaded = false;
  this.children = [];
}
Copy the code

There are three more methods on the prototype Module constructor:

// 1. Require: is used to introduce modules
Module.prototype.require = function(id) {};

// 2. load: used to load modules
Module.prototype.load = function(filename) {};

// 3. _compile: Used to compile and execute module code
Module.prototype._compile = function(content, filename) {};
Copy the code

We’ll explore these three methods later. We can see that when new Module() is called, the instantiated Module looks like this:

module = {
  id: ".".// Module id, usually the absolute path to the file, the root module is reset to '.'
  path: "".// The path to the module folder
  exports: {}, // Exported module value, initial value {}
  parent: {}, // The parent module (deprecated according to the Node documentation, but the actual value is still available)
  filename: "".// The absolute path to the module file (this property is added when the module is loaded)
  loaded: false.// Indicates whether the current module has been loaded
  children: [].// List of submodules
  // Paths from the current path up to the root directory, node_modules at each level.
  // (this is used when require(path) is passed to a third party module, and is added when the module is loaded.)
  paths: [
    // 'D:\\WEB_NOTES\\modules\\Commonjs\\node_modules',
    // 'D:\\WEB_NOTES\\modules\\node_modules',
    // 'D:\\WEB_NOTES\\node_modules',
    // 'D:\\node_modules'].__proto__: {
    require: function(id) {},
    load: function(filename) {},
    _compile: function(content, filename) {},}};Copy the code

We can see that on this instance, there is an exports property that starts with an empty object, the value of module.exports, which is the module we eventually exported, and then when we import in other modules using require(), Module. exports is returned as the return value of the require function. As follows:

// moduleA.js
// Export the module in this file
module.exports = {
  name: "I am moduleA"};Copy the code
// index.js
// Introduce the module moduleA in this file
const moduleA = require("./moduleA.js");

// Output the moduleA value. We can see that the moduleA value is the object we exported in moduleA.js
console.log("moduleA: ", moduleA); // moduleA: { name: 'I am moduleA' }
Copy the code

2. The module. Exports and exports

If you have used Node, you will be familiar with the two apis. In actual use scenarios, it is easy to confuse the two apis and make mistakes. So what are the differences between the two apis?

In the Node source code, there are a few lines that explain their relationship:

// 1. Set = to make exports a copy of module.exports
// Where 'this' is our module instance
const exports = this.exports; // exports = module. Exports;

// the return value of the require function is as follows:
const require = function(id) {
  // ...
  return module.exports;
};
Copy the code

Exports and module.exports refer to the same address, and their initial value is an empty object, but module.exports is returned.

  1. Exports and module.exports are both objects, so we useexports.key = valuemodule.exports.key = valueIn the form of, the results are equivalent.
// moduleA.js
module.exports.name = "I am moduleA";
exports.age = 20;
Copy the code
// index.js
const moduleA = require("./moduleA.js");
console.log("moduleA: ", moduleA); // moduleA: { name: 'I am moduleA', age: 20 }
Copy the code

We can see that the name and age values are exported normally.

  1. becauserequireThe final return of the function ismodule.exportsSo if we use themodule.exports = newValueReassigning a value to the form ofmodule.exportsandexportsThe link was broken. At this point, if we also useexports.key = valueIs assigned to the exported module in the form ofexports.keyLost value of:
// moduleA.js
exports.age = 20;

module.exports = {
  score: 100};Copy the code
// index.js
const moduleA = require("./moduleA.js");
console.log("moduleA: ", moduleA); // moduleA: { score: 100 }
Copy the code

So, if we’re going to export modules using module.exports = newValue, don’t use exports.

  1. You can’t useexports = valueExport the module in the form of, otherwise it will also causemodule.exportsandexportsThe connection was broken, resulting inexports = valueThe value of the:
// moduleA.js
exports = 20;

module.exports.score = 100;
Copy the code
// index.js
const moduleA = require("./moduleA.js");
console.log("moduleA: ", moduleA); // moduleA: { score: 100 }
Copy the code

3. How to determine whether the current module is a root module or a submodule?

On the require function, there is a main attribute that refers to the root module in the current module reference chain, as determined by require.main === module, which returns true if the current module is the root module, and false for submodules.

4. Referencing the same module multiple times (module cache)

Within the reference mechanism of modules, when a module is successfully loaded once, it will be written into the cache. The specific cache information can be viewed by printing require.cache. On the second load, the data is read directly from the cache instead of reloading and executing the code inside the module. So when you introduce the same module more than once, the code inside that module will not be executed more than once. As follows:

// moduleA.js
module.exports.time = Date.now();
Copy the code
// index.js
const moduleA1 = require("./moduleA");

console.log("moduleA1 = ", moduleA1);

console.log("require.cache = ".require.cache);

const moduleA2 = require("./moduleA");

console.log("moduleA2 = ", moduleA2);

// The output is as follows:
// console.log("moduleA1 = ", moduleA1);
moduleA1 = { time: 1606017634808 }

// console.log("require.cache = ", require.cache);
require.cache: {
  'E:\\WEB_NOTES\\modules\\Commonjs\\index.js': {
    id: '. '.path: 'E:\\WEB_NOTES\\modules\\Commonjs'.exports: {},
    parent: null.filename: 'E:\\WEB_NOTES\\modules\\Commonjs\\index.js'.loaded: false.children: [ [Module] ],
    paths: [
      'E:\\WEB_NOTES\\modules\\Commonjs\\node_modules'.'E:\\WEB_NOTES\\modules\\node_modules'.'E:\\WEB_NOTES\\node_modules'.'E:\\node_modules']},'E:\\WEB_NOTES\\modules\\Commonjs\\moduleA.js': {
    id: 'E:\\WEB_NOTES\\modules\\Commonjs\\moduleA.js'.path: 'E:\\WEB_NOTES\\modules\\Commonjs'.exports: { time: 1606017634808 },
    parent: Module {
      id: '. '.path: 'E:\\WEB_NOTES\\modules\\Commonjs'.exports: {},
      parent: null.filename: 'E:\\WEB_NOTES\\modules\\Commonjs\\index.js'.loaded: false.children: [Array].paths: [Array]},filename: 'E:\\WEB_NOTES\\modules\\Commonjs\\moduleA.js'.loaded: true.children: [].paths: [
      'E:\\WEB_NOTES\\modules\\Commonjs\\node_modules'.'E:\\WEB_NOTES\\modules\\node_modules'.'E:\\WEB_NOTES\\node_modules'.'E:\\node_modules']}}// console.log("moduleA2 = ", moduleA2);
moduleA2 = { time: 1606017634808 }
Copy the code

We can see that after the moduleA is successfully loaded for the first time, the time value is 1606017634808, and we can also see by printing require.cache that the result of the module has been added to the cache. ModuleA was loaded the second time directly from the cache without re-executing moduleA’s module.exports.time = date.now (), so 1606017634808 was returned.

5. Circular reference, will it cause an endless loop?

There are no dead loops, thanks to the module’s two cache priorities:

1. When there is data of the current module in the cache, the value is directly taken from the cache, instead of loading and executing the code inside the module; 2. If there is no cache, module instance will be added to the cache first after module instance is created. The initial value of module exports is empty object ({}), and then it will load and execute the code inside the module.Copy the code

In cyclic reference, it will return to the time when the first module needs to be loaded, because at this time the cache has the cached value of the first module, so it will directly return its cached value, and will not execute the code in the module, so it will not cause a second loop, and there is no infinite loop. Let’s look at an example:

// A.js
module.exports = "A1";
const moduleB = require("./B.js");
console.log("moduleB: ", moduleB);
module.exports = "A2";

// B.js
module.exports = "B1";
const moduleA = require("./A.js");
console.log("moduleA: ", moduleA);
module.exports = "B2";

// For the above two modules, we assume that module A was loaded first;
// Then the internal prints of the above two modules will be output successively:
// "moduleA: A1"
// "moduleB: B2"
Copy the code

6. Is the introduction of modules synchronous or asynchronous?

The internal mechanism of the module is to synchronize the contents of the file read by fs.readfilesync () under the fs module built in Node. After the reading is completed, the parsing logic is also executed synchronously. Therefore, the module is introduced synchronously. The logic behind the reference module is executed.

7. Which file types are supported by require by default?

Js /. Json /. Node file types are supported by default.

Exports, require, module, __filename, __dirname from where?

Fs.readfilesync () converts all the code inside each module (file) into a string, and wraps it externally with a wrapper function. This wrapper function accepts exports, require, module, __filename, __dirname as arguments, as follows: exports, require, module, __filename, __dirname

function compiledWrapper(exports.require.module, __filename, __dirname) {
  Var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var
}
Copy the code

Because the module code is executed inside the wrapper function, you can get all the arguments to the function, i.e. Exports, require, module, __filename, __dirname, etc.

9. Why are variables inside a module invisible to other modules?

As explained in the previous problem, because the code inside the module is executed inside the wrapper function, the variables inside the module belong to the function scope, and each module has such an independent function scope, so the variables between them are not mutually accessible.

10. The id passed in when require(id) is called.

  1. First, check whether it is a local module (Node built-in module) by id. If it is a local module, the local module will be returned directly.

  2. Determine whether the ID is a relative path and locate the module according to different rules:

  • If it is not a relative path, there are two cases:

    Absolute path: the absolute path is used directly to find modules;

    For third-party modules, the system searches the node_modules folder in each layer from the directory where the file resides to the root directory. If the corresponding module can be found, the system returns its absolute path.

  • Resolve (__dirname, id) is used to get the absolute path of the module to which the ID corresponds, and this absolute path is used to find the module.

  1. When the module is found by path in the previous step, it first determines whether the path can be asserted as a folder:
  • If the path cannot be identified as a folder, it is parsed as a file. If the path does not end with a suffix, it is parsed with the suffix. Js /. Json /. Node.

  • If it can be asserted as a folder, the package.json file in the folder will be loaded, its internal main property value will be read, and the module will be found according to the path of the main property value. If there is no package.json or the main attribute has no value, the.js/.json/.node suffix is used in sequence to find the index file in the folder.

Conclusion: We answered each of the initial questions in turn above, but these answers are only superficial, if you still want to trace back to the source, then let’s follow the CommonJS module source code to explore.


Second, source code interpretation

1. The definition of require

We use require in modules to import other modules, but the require function is exactly what it is, so I’ll go back to the source code:

// The definition of require in the source code looks something like this:
const require = makeRequireFunction(this, redirects);

// makeRequireFunction defines the function
function makeRequireFunction(mod, redirects) {
  let require;

  require = function require(path) {
    return module.require(path); // module is our module
  };

  // Then, on the require function, we define several useful attributes:
  // require.resolve resolves the absolute path of the module based on the parameter values passed in.
  require.resolve = resolve(request, options) {
    return Module._resolveFilename(request, module.false, options);
  };

  // Process. mainModule holds the root module, where require.main also refers to the root module
  require.main = process.mainModule;

  // Enable support to add extra extension types.
  // This was originally intended to extend the types of files require can support, but Node documentation explains this
  // It has been deprecated.
  require.extensions = Module._extensions;

  // module. _cache holds information about the Module's cache, requiring.
  require.cache = Module._cache;

  // Each attribute value in the require function above is important, and we'll explain what it does later.

  return require;
}
Copy the code

Module.prototype.require (); require (); require ();

Module.prototype.require = function(id) {
  validateString(id, "id");
  if (id === "") {
    throw new ERR_INVALID_ARG_VALUE("id", id, "must be a non-empty string");
  }
  requireDepth++;
  try {
    // We can ignore the rest of the code for now
    return Module._load(id, this./* isMain */ false);
  } finally{ requireDepth--; }};Copy the code

2. Require parsing rules

2.1 Three Parsing rules

Rule 1: try to read the cache Rule 2: Try to load a local module Rule 3: Use a file to generate a new moduleCopy the code

The logic of the require parsing rule is handled primarily inside the module. _load function:

Module._load = function(request, parent, isMain) {
  // The source code for this function is annotated, translated as follows:
  // 1. If the module is already in the cache: returns its exports object.
  // 2. If the module is a local module: find it and return its exports.
  // 3. Otherwise, create a new module for the file and save it to the cache. It then loads, executes the contents of the file, and returns its exports object.
  // These are the three main parsing rules of require
};
Copy the code

2.2 Rule 1: Try to read cache

Module._load = function(request, parent, isMain) {
  // Check the cache
  let relResolveCacheIdentifier;
  if (parent) {
    relResolveCacheIdentifier = `${parent.path}\x00${request}`;
    const filename = relativeResolveCache[relResolveCacheIdentifier];
    if(filename ! = =undefined) {
      // Check whether the Module is cached, and its cache is in module._cache
      const cachedModule = Module._cache[filename];
      if(cachedModule ! = =undefined) {
        // If there is a cache, add the module to the children list of the parent module
        updateChildren(parent, cachedModule, true);

        // Loaded is used to indicate whether the current module has been loaded.
        // In this case, the module is cached but not loaded. The only case is when the module is loaded.
        // references another module, which in turn references the current module, thus creating a circular reference.
        // So, this is where circular references are handled.
        if(! cachedModule.loaded) {// (Emphasis 1) Handle circular references
          return getExportsForCircularRequire(cachedModule);
        }
        // If the module has been loaded, the module's exports object is returned.
        return cachedModule.exports;
      }
      deleterelativeResolveCache[relResolveCacheIdentifier]; }}// (emphasis 2) Parse the file path:
  // 1. If it is a local module, return the name of the module
  // 2. In other cases, return the absolute path of the file where the module resides
  const filename = Module._resolveFilename(request, parent, isMain);

  // Check the cache again
  const cachedModule = Module._cache[filename];
  if(cachedModule ! = =undefined) {
    updateChildren(parent, cachedModule, true);
    if(! cachedModule.loaded) {const parseCachedModule = cjsParseCache.get(cachedModule);
      if(! parseCachedModule || parseCachedModule.loaded)return getExportsForCircularRequire(cachedModule);
      parseCachedModule.loaded = true;
    } else {
      returncachedModule.exports; }}// If there is no cache, execute rule 2...
};
Copy the code

In this step, an attempt is made to read Module data from module-_cache. If there is a cache, return the cached data, otherwise proceed to rule 2 (load local modules).

In this step, there are two points to pay attention to: “Handling circular references” and “Resolving file paths”, see: Handling circular references and resolving file paths respectively

2.3 Rule 2: Try to load a local module

Module._load = function(request, parent, isMain) {
  // const filename = Module._resolveFilename(request, parent, isMain);

  // If there is no cache, load the local module (Node built-in module) (emphasis 1)
  const mod = loadNativeModule(filename, request);
  if (mod && mod.canBeRequiredByUsers) {
    return mod.exports;
  }

  // If there is no local module, execute rule 3...
};
Copy the code

In this step, there is one point to pay attention to: “Loading local module concrete implementation”, see: Loading local module concrete implementation.

2.4 Rule 3: Generate new modules using files

Module._load = function(request, parent, isMain) {
  // const filename = Module._resolveFilename(request, parent, isMain);

  // If there is neither a cache nor a local module, create a new module for the file and add it to the cache.
  // The implementation of new Module() is described earlier.
  const module = cachedModule || new Module(filename, parent);

  if (isMain) {
    // If it is the root module, save the module to process.mainModule,
    // Require. Main = process.mainModule;
    // require. Main = process.mainModule= module; It's connected.
    // To return to our previous question: how do I determine whether the current module is a root module or a submodule?
    // The root module returns true and the submodules false, as determined by require.main === module
    process.mainModule = module;

    // The module id is usually the absolute path to the file. The root module is reset to '.'.
    // The reason comes from here
    module.id = ".";
  }

  // Write module to the module._cache
  // require. Cache = module. _cache;
  // in this case, require. Cache also points to the module's cache. The reason for this is that we can't get it inside the module
  // module. _cache, but we can get the require function, so when we need to manually clear the cache,
  // Delete the cache using delete require.cache[filename].
  // The absolute path filename can be obtained via require.resolve(the relative path to the module to be deleted).
  Module._cache[filename] = module;

  if(parent ! = =undefined) {
    relativeResolveCache[relResolveCacheIdentifier] = filename;
  }

  let threw = true;
  try {
    // Intercept exceptions that occur during the first tick and rekey them
    // on error instance rather than module instance (which will immediately be
    // garbage collected).
    if (getSourceMapsEnabled()) {
      try {
        module.load(filename);
      } catch (err) {
        rekeySourceMap(Module._cache[filename], err);
        throw err; /* node-do-not-add-exception-line */}}else {
      // (key) load modules according to absolute path and execute module internal code, module.exports assigns value
      module.load(filename);
    }
    threw = false;
  } finally {
    if (threw) {
      // Note here:
      // Since we wrote the cache first and then performed the module load, there is a possibility of module load error
      // In this case (for example, no module can be found), the cache that was written before needs to be cleared and removed from the children list of the parent module
      // Delete the current module.
      delete Module._cache[filename];
      if(parent ! = =undefined) {
        delete relativeResolveCache[relResolveCacheIdentifier];
        const children = parent && parent.children;
        if (ArrayIsArray(children)) {
          const index = ArrayPrototypeIndexOf(children, module);
          if(index ! = = -1) {
            ArrayPrototypeSplice(children, index, 1); }}}}else if (
      module.exports && ! isProxy(module.exports) &&
      ObjectGetPrototypeOf(module.exports) ===
        CircularRequirePrototypeWarningProxy
    ) {
      ObjectSetPrototypeOf(module.exports, ObjectPrototype); }}Module. exports, as the return value when the require function is called.
  return module.exports;
};
Copy the code

In this step, the Module instance is created with new Module() and cached, and then the code inside the file is parsed and executed. If an error occurs during parsing, an error is thrown and the cache is cleared.

How to parse and execute the logic of the internal code of the file, please see: load file module.

2.5 summarize

Require parsing rules: require parsing rules: require parsing

Read cache, load local modules, generate new modules using files.

It also addresses several of the issues we raised:

1. How to determine whether the current module is a root module or a submodule? The root module returns true, the submodule false 2. When the same module is introduced more than once, will the code inside the module be executed more than once? Answer: No. When a module is successfully loaded once, it is written to the cache, and when it is loaded a second time, it reads data directly from the cache, without reloading or executing the code inside the module.Copy the code

At the same time, there are four areas that need to be paid attention to, which are:

  1. Handling circular references
  2. Resolving the file path
  3. Load the concrete implementation of the local module
  4. Loading file module

3. Handle circular references

The logic for handling circular references is simple:

function getExportsForCircularRequire(module) {
  /* if (module.exports && ! isProxy(module.exports) && ObjectGetPrototypeOf(module.exports) === ObjectPrototype && ! module.exports.__esModule) { ObjectSetPrototypeOf( module.exports, CircularRequirePrototypeWarningProxy); } * /

  // exports returns module.exports directly. // exports returns module.exports directly
  return module.exports;
}
Copy the code

Node returns the module.exports value of the module directly when it encounters a circular reference. What is this? This is due to the module’s two cache-first principles:

1. When there is data of the current module in the cache, it directly returns the value of the module exports, instead of loading and executing the code inside the module; 2. If there is no cache, the module instance will be cached first after it is created, and then the code inside the module will be loaded and executed.Copy the code

Let’s look at an example:

// A.js
module.exports = "A1";
const moduleB = require("./B.js");
console.log("moduleB: ", moduleB);
module.exports = "A2";
Copy the code
// B.js
module.exports = "B1";
const moduleA = require("./A.js");
console.log("moduleA: ", moduleA);
module.exports = "B2";
Copy the code

For the above two modules, we assume that module A loaded first, when loading A for the first time, A has no cache, so according to the above two priority principles, it will directly go to the second principle:

1. Create Module instance moduleA and cache it in module. _cache. ModuleA exports = "A1", moduleA exports = "A1", moduleA exports = "A1" Const moduleB = require("./ b.js "); 4. Create Module instance moduleB and cache it in module. _cache. ModuleB = module. Exports = "B1"; moduleB = module. Exports = module. (2) When const moduleA = require("./ a.js "), it will jump inside require and load A.js, and then it will read that the moduleA already exists in the cache and fetch the moduleA from the cache. It returns its exports value, "A1", and does not load and execute the code in A.js, so it does not loop again. Module. exports = "A1" in line 1 of A.js Module. Exports = "A1"; module. Exports = "A1"; module. Exports = "A1"; 6. After the previous step, the command output is moduleA = "A1". 7. Run console.log("moduleA: ", moduleA). Output "moduleA: A1"; ModuleB's module.exports = "B2"; 9. After b.js is executed, it indicates that moduleB is loaded successfully. Return to A.js and get moduleB = "B2". Run console.log("moduleB: ", moduleB); Output "moduleB: B2"; 11. Continued to assign "A2" to moduleA's module.exports;Copy the code

The loop reference logic between the two modules is now complete: from the above we answer the question raised before: when two modules are referenced repeatedly, does it cause an infinite loop?

The answer is: it will not cause an infinite loop, but will return the cached value of the first module when it is loaded a second time without executing the code inside the module, so it will not cause a second loop and there will be no infinite loop.

4. Resolve the file path

Module._load const filename = module. _resolveFilename(request, parent, isMain) const filename = module. _load (request, parent, isMain) How exactly does it resolve the path that we passed in when we called require(path).

Module._resolveFilename = function(request, parent, isMain) {
  // 1. Check whether the required module can be found in the local module. If yes, return the request directly
  // For example, if we introduce path: const HTTP = require(' HTTP ') in the module, we will return HTTP directly
  if (NativeModule.canBeRequiredByUsers(request)) {
    return request;
  }

  // 2. Parse out the list of folders that need to be resolved during the module search
  let paths = Module._resolveLookupPaths(request, parent);

  // 3. Parse and return the absolute path of the module according to Request and Paths
  // Look up the filename first, since that's the cache key.
  const filename = Module._findPath(request, paths, isMain, false);

  // If filename has a value, the module can find it and returns the path
  if (filename) return filename;

  // If no module is found, an error will be reported
  const requireStack = [];
  for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) {
    ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
  }
  let message = `Cannot find module '${request}'`;
  if (requireStack.length > 0) {
    message =
      message +
      "\nRequire stack:\n- " +
      ArrayPrototypeJoin(requireStack, "\n- ");
  }
  // eslint-disable-next-line no-restricted-syntax
  const err = new Error(message);
  err.code = "MODULE_NOT_FOUND";
  err.requireStack = requireStack;
  throw err;
};
Copy the code

4.1 Checking whether the required module can be found in the local module

NativeModule.canBeRequiredByUsers = function(id) {
  // NativeModule. Map is an array containing all local modules of Node,
  // We can get the module by passing the module id through the get method
  const mod = NativeModule.map.get(id);
  return mod && mod.canBeRequiredByUsers;
};
Copy the code

4.2 Parsing Paths (List of folders to look for in the process of finding modules)

Module._resolveLookupPaths = function(request, parent) {
  // 1. If the request does not start with a '.' or if it does start with a '.' but is not a relative path, then the if judgment is true.
  // File types like '.gitignore', '.babelrc'.
  // (in other words, if request is not a relative path, then this if judgment is true, such as third-party modules 'axios', 'webpack',
  // or absolute paths such as '/home/user', '.gitignore', 'D:\\WEB_NOTES\\modules\\Commonjs', etc.)
  if (
    StringPrototypeCharAt(request, 0)! = ="." ||
    (request.length > 1 &&
      StringPrototypeCharAt(request, 1)! = ="." &&
      StringPrototypeCharAt(request, 1)! = ="/"&& (! isWindows || StringPrototypeCharAt(request,1)! = ="\ \"))) {let paths = modulePaths;
    /* modulePaths is a list of paths similar to the following for finding globally installed modules, where $HOME is the user's HOME directory, Node_modules ", "$HOME\.node_libraries", "$PREFIX\lib\ Node "] */

    /* parent-paths path of the parent module, look up to the root directory, node_modules of each level, Mainly used to find partially-installed third-party modules ["E:\WEB_NOTES\modules\Commonjs\node_modules", "E:\WEB_NOTES\modules\node_modules", "E:\WEB_NOTES\node_modules", "E:\node_modules" ] */
    if(parent ! =null && parent.paths && parent.paths.length) {
      // Concatenate modulePaths and parent.paths together using the concat method of arrays
      paths = ArrayPrototypeConcat(parent.paths, paths);
    }
    return paths.length > 0 ? paths : null;
  }

  // 2. If request is a relative path, use path.dirname to resolve the folder path of the parent module
  const parentDir = [path.dirname(parent.filename)];
  return parentDir;
};
Copy the code

As you can see from the above code, there are two main cases for parsing paths:

1. If request is not a relative path, node_modules is searched from the global module's installation directory or from the parent module's directory to the root directory. 2. If request is a relative path, the parent module's directory is resolved and returned (then the parent module's directory is added to the relative path).Copy the code

4.3 Resolving the Absolute Path of the Module

Module._findPath = function(request, paths, isMain) {
  // Check whether request is an absolute path
  const absoluteRequest = path.isAbsolute(request);
  if (absoluteRequest) {
    // For absolute paths, you don't need paths because absolute paths can be used directly.
    // For example: 'D:\\WEB_NOTES\\modules\\Commonjs\\ modulea.js'
    paths = [""];
  } else if(! paths || paths.length ===0) {
    return false;
  }
  // "\x00" is space: ""
  const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00");

  If the path is cached, the path is returned directly. No further parsing is required to save performance
  const entry = Module._pathCache[cacheKey];
  if (entry) return entry;

  let exts;

  // Check whether request ends with a slash '/' to check whether request is a directory
  let trailingSlash =
    request.length > 0 &&
    StringPrototypeCharCodeAt(request, request.length - 1) ===
      CHAR_FORWARD_SLASH;
  if(! trailingSlash) {// Check whether request starts with '.', '.. ', '/', '/.. 'this style ends
    trailingSlash = RegExpPrototypeTest(trailingSlashRegex, request);
  }

  // For each path
  for (let i = 0; i < paths.length; i++) {
    // Don't search further if path doesn't exist
    const curPath = paths[i];
    if (curPath && stat(curPath) < 1) continue;

    if(! absoluteRequest) {const exportsResolved = resolveExports(curPath, request);
      if (exportsResolved) return exportsResolved;
    }

    // Join the paths in Paths and request in turn to form absolute paths
    const basePath = path.resolve(curPath, request);
    let filename;

    const rc = stat(basePath); BasePath is used to determine whether basePath has a file suffix
    if(! trailingSlash) {if (rc === 0) {
        // rc === 0, the file suffix is available, and the path can be used directly
        // If you go further than that, you're going to get to the bottom of the C code, so I won't go further.
        if(! isMain) {if (preserveSymlinks) {
            filename = path.resolve(basePath);
          } else{ filename = toRealPath(basePath); }}else if (preserveSymlinksMain) {
          // This is mainly for backward compatibility
          filename = path.resolve(basePath);
        } else{ filename = toRealPath(basePath); }}// If the basePath does not find the corresponding file, the basePath will add.js/.json/.node extension, and then search for it
      if(! filename) {// Try it with each of the extensions
        if (exts === undefined) exts = ObjectKeys(Module._extensions);

        // Add.js/.json/.node, etc
        filename = tryExtensions(basePath, exts, isMain);
        /* tryExtension function // Given a path, check if the file exists with any of the set extensions function tryExtensions(p, exts, isMain) { for (let i = 0; i < exts.length; i++) { const filename = tryFile(p + exts[i], isMain); if (filename) { return filename; } } return false; } * /}}// If it is a directory, the files inside the directory are read
    /* tryPackage mainly does the following three things: * 1. If there is a package.json file in the directory, the file will be read first, and the value of the main property inside; * 2. If the main attribute has a value, path.resolve(basePath, main) will be used to concatenate the path, and then repeat the above method to check whether the file can be found. 3. If package.json is not available or the main attribute has no value, the basePath index file */ will be loaded with.js/. Json /
    if(! filename && rc ===1) {
      // Directory.
      // try it with each of the extensions at "index"
      if (exts === undefined) exts = ObjectKeys(Module._extensions);
      filename = tryPackage(basePath, exts, isMain, request);
    }

    if (filename) {
      // Write the resulting absolute path to the cache for the next read directly from the cache without multiple parses
      Module._pathCache[cacheKey] = filename;
      returnfilename; }}return false;
};
Copy the code

The _findPath method above is a bit long, but it basically does the following things: ·

1. If request is an absolute path, the Paths list is discarded. Parse the end of the request to determine whether the request is a directory. 3. If it cannot be determined that it must be a directory, parse the file according to the rules first: (1) Use the path inside paths to join the request and get basePath as the path to search the file; (2) If the basePath path has a suffix, basePath will try to find the file directly. If it does not find the file, it will try to use.js/.json/.node suffix after it and try to find the file again. (1) If there is a package.json file in the directory, the file will be read first, and the internal main property value will be read; Resolve (basePath, basePath, basePath, basePath, basePath, basePath, basePath, basePath); (3) If there is no package.json or the main attribute has no value, the index file in basePath will be loaded with.js/.json/.node suffixCopy the code

5. Load the local module

const mod = loadNativeModule(filename, request);
if (mod && mod.canBeRequiredByUsers) return mod.exports;

// loadNativeModule definition
function loadNativeModule(filename, request) {
  // NativeModule. Map stores all local modules
  const mod = NativeModule.map.get(filename);
  if (mod) {
    returnmod; }}Copy the code

NativeModule. Map (NativeModule. Map) : NativeModule (NativeModule. Map) : NativeModule.

6. Load the file module

6.1 the Module. The prototype. The load

module.load(filename);

/ / module. The load function
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
  // If the module is already loaded, an error will be thrown.
  assert(!this.loaded);

  // Assign filename to the module
  this.filename = filename;

  // Add the paths property value for use when loading third-party modules.
  // Paths from the current path up to the root directory, node_modules at each level.
  // Paths values look like this:
  /* [ 'D:\\WEB_NOTES\\modules\\Commonjs\\node_modules', 'D:\\WEB_NOTES\\modules\\node_modules', 'D:\\WEB_NOTES\\node_modules', 'D:\\node_modules' ] */
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  // Resolve the extension of the file based on the absolute path of the file
  const extension = findLongestRegisteredExtension(filename);

  // Load the file using the corresponding method according to the file name extension
  Module._extensions[extension](this, filename);

  // Set loaded to true to indicate that the module is loaded.
  this.loaded = true;
};
Copy the code

Module._extensions[extension] has 3 properties by default: .js,.json, and.node are the types of files that require can import by default, which brings us back to our previous question:

1. Which file types are supported by require by default? Solutions:.js,.json,.nodeCopy the code

6.2 the Module. _extensions [‘ js’]

1. Read the file content synchronously

// Native extension for .js
Module._extensions[".js"] = function(module, filename) {
  if (StringPrototypeEndsWith(filename, ".js")) {
    const pkg = readPackageScope(filename);
    // Function require shouldn't be used in ES modules.
    if (pkg && pkg.data && pkg.data.type === "module") {
      const parent = moduleParentCache.get(module);
      const parentPath = parent && parent.filename;
      const packageJsonPath = path.resolve(pkg.path, "package.json");
      throw newERR_REQUIRE_ESM(filename, parentPath, packageJsonPath); }}// Use fs.readFileSync to read file contents synchronously. The return value is a string of file contents
  content = fs.readFileSync(filename, "utf8");
  // Compile the file contents
  module._compile(content, filename);
};
Copy the code

2. Compile and execute files synchronously

Module.prototype._compile = function(content, filename) {
  let moduleURL;
  let redirects;
  // ***** is important here:
  The wrapSafe function returns an underlying wrapper function that wraps the content, something like this:
  /* function (exports, require, module, __filename, __dirname) {eval(content) Instead of eval, the Node VM module uses eval for ease of understanding} */
  // So that all the code we wrote inside the module is executed inside this wrapper function, and therefore, other modules
  // Can not access the internal variables of this module
  const compiledWrapper = wrapSafe(filename, content, this);

  // Define the dirname variable
  const dirname = path.dirname(filename);

  // Define the require function (back to section 3.1: require)
  const require = makeRequireFunction(this, redirects);
  let result;
  // create duplicate exports for module.exports
  const exports = this.exports;
  const thisValue = exports;
  const module = this;
  Apply (thisValue, [exports, require,... )
  // The call here is critical:
  // exports, require, module, filename, dirname are passed to compiledWrapper.
  // Let the compiledWrapper function we got above look like this:
  // function (exports, require, module, __filename, __dirname) { eval(content) }
  // Exports, require, exports. exports. require, exports. require
  // module, __filename, __dirname, etc., and why they can be used without defining them.
  result = ReflectApply(compiledWrapper, thisValue, [
    exports.require.module,
    filename,
    dirname,
  ]);
  return result;
};
Copy the code

As can be seen from the above two steps of reading and executing file contents, the introduction of modules is synchronous execution. It’s time to answer some of our first questions:

1. Is the introduction of modules synchronous or asynchronous? Exports, require, module, __filename, and __dirname are exports, require, module, and __dirname. Answer: it comes from a wrapper function added in the outer layer when executing the module code. This wrapper function accepts exports, require, module, __filename, __dirname, and the module code is executed inside the wrapper function, so you can get these values directly. 3. Why are variables inside a module invisible to other modules? Solution: Because the code inside a module is executed inside a wrapper function, all the variables inside it belong to the scope of this function, so other modules are not accessible.Copy the code

6.3 Module. _extensions [‘ json ‘]

// Native extension for .json
Module._extensions[".json"] = function(module, filename) {
  // Read the contents of the JSON file synchronously
  const content = fs.readFileSync(filename, "utf8");

  try {
    // attempts to parse the JSON string into an object and assign it to module.exports
    module.exports = JSONParse(stripBOM(content));
  } catch (err) {
    err.message = filename + ":" + err.message;
    throwerr; }};Copy the code

6.4 Module. _extensions [‘ node ‘]

// Native extension for .node
Module._extensions[".node"] = function(module, filename) {
  if(policy? .manifest) {const content = fs.readFileSync(filename);
    const moduleURL = pathToFileURL(filename);
    policy.manifest.assertIntegrity(moduleURL, content);
  }
  // Be aware this doesn't use `content`
  return process.dlopen(module, path.toNamespacedPath(filename));
};
Copy the code