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 dug into node's core code to find out what was going on under the hood. But this is not a single function. I found module.js in node's module system. This file contains a surprisingly powerful and relatively unfamiliar core module that controls the loading, compilation and caching of each file. `require()`, its emergence is just the tip of the iceberg.
module.js
The second major task of this module is to handle the module loading mechanism of node. The independent operation "require" function we use is actually an abstract concept of module.require, which itself is just a simple encapsulation of the Module._load function. This load method handles the actual loading of each file and starts our journey there.
Module._load
Module._load is responsible for loading new modules and managing module cache. Caching each module loaded reduces the number of redundant file reads and can significantly speed up your application. Additionally, shared module instances allow singleton features of modules to remain in the project's state.
If a module does not exist in the cache, Module._load will create a new base module of that file. It then tells the module to read the contents of the new files 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. This is why when you define a public interface to use, you use exports and module.exports, because Module._load will next return the required content. I'm surprised there aren't more features here, but it would be nice if there were.
module._compile
· This is where the real magic happens. First, a special stand-alone require function is created for this module. This is a feature we all need and are familiar with. The function itself is just a package in Module.require, and it also contains some little-known auxiliary methods that are easy for us to use:
· require(): Load an external module
· require.resolve(): Resolve a module name to its absolute path
· require.main:main module
· require.cache: all cached modules
· ·require.extensions: The available compilation methods for each valid file type according to its extension
Once require is ready, the entire loaded source code is encapsulated in a new function that can accept require, module, exports and all other exposed variables as parameters. This is a function created solely to encapsulate the module in order to prevent conflicts with the Node.js environment.
Conclusion
So, we have understood the entire code of require and have a preliminary understanding of how it works.
If you've followed all this, then you're ready for the final secret: require('module'). That's right, the module system itself can be loaded through the module system. Inception. This may sound strange, but it allows user space to interact with the module loading system without having to delve into Node.js core. Popular modules are built like this. [2]
If you want to know more, check out the module.js source code yourself. There are enough things to give you a headache for a while. Can the first one tell me what is NODE_MODULE_CONTEXTS" and why it is added and people who add it can get bonus points :)
[1] The module._compile method is only used to run JavaScript files. JSON files need to be parsed through JSON.parse() and returned
[2] However, both modules are built on private module methods such as Module._resolveLookupPaths and Module._findPath. You can think it's not much better...