nodeblogger.com-how-require-works

Node JS : How require(); works

Featured, Node JS

So every node developer knows require() very well and knows how to use it.

const http = require("http");

But do you know how require works behind the scene. I have read many articles and try to understand it. So let me describe you in the simple way

So the first of all functions which enters in call stack whenever node starts or call a function is Module.runMain which is written in loader.js (/<node_internals>/internal/modules/cjs/laoder.js) .

Module.runMain is main reason to load all modules and attach it to module.exports.

// bootstrap main module.
Module.runMain = function() {
  // Load the main module--the command line argument.
  if (experimentalModules) {
    if (asyncESM === undefined) lazyLoadESM();
    asyncESM.loaderPromise.then((loader) => {
      return loader.import(pathToFileURL(process.argv[1]).pathname);
    })
    .catch((e) => {
      decorateErrorStack(e);
      console.error(e);
      process.exit(1);
    });
  } else {
    Module._load(process.argv[1], null, true);
  }
  // Handle any nextTicks added in the first tick of the program
  process._tickCallback();
};

So Module.runMain calls function Module._load which does the below things.

  1. Check the cache for the requested file.
  2. If a module already exists in the cache: return its exports object.
  3. If the module is native: call `NativeModule.require()` with the filename and return the result.
  4. Otherwise, create a new module for the file and save it to the cache.
  5. Then have it load the file contents before returning its exports object.
Module._load = function(request, parent, isMain) {
  if (parent) {
    debug('Module._load REQUEST %s parent: %s', request, parent.id);
  }
  var filename = Module._resolveFilename(request, parent, isMain);

  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }

  if (NativeModule.nonInternalExists(filename)) {
    debug('load native module %s', request);
    return NativeModule.require(filename);
  }

  // Don't call updateChildren(), Module constructor already does.
  var module = new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }

  Module._cache[filename] = module;

  tryModuleLoad(module, filename);

  return module.exports;
};

Module._load doesn’t finds module in cache (checks Module._cache function to load module from cache) it returns the object directly to module.exports.

If module is not exist in cache it loads the module file using NativeModule.require() returns the module and create a new module instance, save it to cache and return object directly to module.exports.

But here comes the catch and a important step when before preparing the content for module.exports . It checks the module extension (js, json, node) reads the file using fs module and compiles the module code using module._compile.

Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(stripBOM(content), filename);
};

module._compile do the below steps

  1. Run the file contents in the correct scope or sandbox.
  2. Expose the correct helper variables (require, module, exports) to the file.
  3. Returns exception, if any.

So here is the summary, Once require is introduce in a file it loads all source code wrapped in a function having require, module and exports and gives you a nice clean way to use a functionality.

(function (exports, require, module, __filename, __dirname) {
  
});

Other helper function in loader.js are:

Module._extensions : Have compilation method for different extensions.

Module._resolveFilename : Resolves a module name to its absolute path.

Module.runMain : Load the main module and runs Module._load.

Leave a Reply