Simple analysis of the working principle of require in node. js

  • 2020-03-30 03:26:15
  • OfStack

Almost any node. js developer can tell you what the 'require()' function does, but how many of us actually know how it works? We use it every day to load libraries and modules, but its behavior is a mystery to us.

Out of curiosity, I delved into node's core code to find out what was going on under the engine. However, this is not a single function. I found module.js in the module system of node. The file contains a surprisingly powerful and relatively unfamiliar core module that controls the loading, compilation, and caching of each file. 'require ()', which comes out of nowhere, is just the tip of the iceberg.

The module. Js


function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...

Module. js has two main roles within node. js. First, it provides a foundation for all node. js modules. Each file is a new instance of the basic module new that still exists even after the file has been run. That's why we can append genera to module.exports and return them as needed.

The second big task for this module is to handle node's module loading mechanism. The stand-alone "require" function we use is actually an abstract concept called module.require, which in itself is just a simple wrapper around the module._load function. This load method handles the actual load of each file and starts our journey there.

The Module. _load


Module._load = function(request, parent, isMain) {
  // 1. Check Module._cache for the cached module.
  // 2. Create a new Module instance if cache is empty.
  // 3. Save it to the cache.
  // 4. Call module.load() with your the given filename.
  //    This will call module.compile() after reading the file contents.
  // 5. If there was an error loading/parsing the file,
  //    delete the bad module from the cache
  // 6. return module.exports
};

Module._load is responsible for loading new modules and managing the cache of modules. Each module loaded by the cache reduces the number of reads of redundant files and can significantly speed up your application. In addition, Shared module instances allow singleton feature modules to remain in state in the project.

If a Module does not exist in the cache, module._load creates a new base Module for the file. It then tells the module to read the contents of the new file before sending them to module._compile. [1]

If you notice step # 6 above, you will see that module.exports has been returned to the user. That's why when you define a public interface to use, you use exports and module.exports, because module._load will next return something require. I'm surprised there aren't more features, but it would be nice if there were.

The module. _compile


Module.prototype._compile = function(content, filename) {
  // 1. Create the standalone require function that calls module.require.
  // 2. Attach other helper methods to require.
  // 3. Wraps the JS code in a function that provides our require,
  //    module, etc. variables locally to the module scope.
  // 4. Run that function
};

, this is where real miracles happen. First, a special stand-alone require function is created for the module. This is a feature we all need and are all familiar with. The function itself is just a wrapper around module. require, and it also contains some little-known helper methods that are easy to use:

, require() : load an external module
, require. Resolve () : resolves the absolute path of a module name to it
, require. Main: main module
, require. Cache: all cached modules
, , require.extensions: the orchestration method available for each valid file type, depending on its extension

Once require is ready, the entire loaded source code is encapsulated in a new function that accepts require,module,exports, and any other exposed variables. This is a function created only to encapsulate the module in order to prevent conflicts with the node. js environment.


(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

The module._compile method is executed synchronously, so calls to module._load have to wait until the code is run and return module.exprts to the user.

conclusion

So, we've seen all the code for require, and we've had a taste of how it works.

If you've done all this, you're ready for the final secret: require('module'). This is correct and the module system itself can be loaded through the module system. Inception. This may sound strange, but it allows the user space to interact with the module loading system without delving into the node.js core. Popular modules are built like this. [2]

If you want to learn more, check out the module.js source for yourself. There are plenty of things that will give you a headache for a while. The first one can tell me what is NODE_MODULE_CONTEXTS "and why it can be added to people for bonus points :)

The [1] module._compile method is only used to run JavaScript files. The JSON file is parsed and returned via json.parse ()

[2] however, both modules are built on private Module methods such as module._resolvelookuppaths and module._findpath. You can argue that it's not much better...


Related articles: