How NodeJs Processes Modules?

Sharing is Caring

We all know that to execute a node application, we have to type the command. In this post, we’ll see how NodeJS processes Modules.

node app.js

where app.jsis the entry point to our application.

But seldom do we wonder about what’s happening when we type this command. How does the node magically come to take our js file and executes it? This is exactly what we’ll be discussing in this article.

In another article, I described how nodejs passes the js file to the V8 engine and converts it into processor comprehensible language. There’s a catch here. Node doesn’t send the content of entry module(app.js) directly to V8. First, it processes this code and augments it by wrapping it inside a wrapper function as shown below.

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

    // content of app.js

});

After wrapping the code, Node passes it through V8 engine for compilation into m/c code. So how does node accomplish that? What are the steps involved to achieve this? Let’s talk about this in detail.

 

It starts with runMain function which is bootstrap main module (analogous to C++ main() function) for node (this is not the first function to execute actually. Call stack goes like listOnTimeout -> tryOnTimeout -> ontimeOut -> Module.runMain ….). When we type node app.js, the node environment executes this main module.

...
...

Module._load(process.argv[1], null, true);
...
...

Here process.argv[1]corresponds to the string "app.js".  So basically node is being instructed to _loadthe file app.js.

 

In the Module._load function, a new Module object is created and then this module is passed to tryModuleLoadfunction for loading.

var module = new Module(filename, parent);
...
tryModuleLoad(module, filename);

tryModuleLoad loads the module.

...

module.load(filename)

...

Modulefunction’s load performs a check on the extension. If no extension is specified, it takes default extension to be .js

...
var extension = findLongestRegisteredExtension(filename);
Module._extensions[extension](this, filename);
...

Since our file extension is .jsModule._extension['.js']will be called.

var content = fs.readFileSync(filename, 'utf8');

module._compile(stripBOM(content), filename);

Following things happens in this function.

// Run the file contents in the correct scope or sandbox. Expose

// the correct helper variables (require, module, exports) to the file.

// Returns exception, if any.

Each module has exports, require, module, __filename__dirnameand thisattached to it. this comes as per EcmaScript standard. This is where these properties are declared for this module.

var dirname = path.dirname(filename);

var require = makeRequireFunction(this);

var result;

var exports = this.exports;

var thisValue = exports;

var module = this;

And then there is something about wrapping the code,

const wrapper = Module.wrap(content);

We set to find out what Module.wrap does, which is nothing but

let wrap = function(script) {

  return Module.wrapper[0] + script + Module.wrapper[1];

};


const wrapper = [

  '(function (exports, require, module, __filename, __dirname) { ',

  '\n});'

];

This is how our programs have access to the magic variables exports, require, module, __filename, and __dirname

Now we can see how we’ve got our wrapped function we talked about initially.

Then this wrapped function is compiled and executed here with runInThisContextdefined in vm.jsmodule

compiledWrapper = vm.runInThisContext(wrapper, {

      filename,

      lineOffset: 0,

      displayErrors: true,

      importModuleDynamically: experimentalModules ? async (specifier) => {

        if (asyncESM === undefined) lazyLoadESM();

        const loader = await asyncESM.loaderPromise;

        return loader.import(specifier, normalizeReferrerURL(filename));

      } : undefined,

});

Then finally the module’s compiled wrapped function object is invoked like this, with values populated for exports, require, module, __filename and __dirname

result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname);

This value is then finally returned

return result;

I hope this would have provided you with a deep understanding of the node and the processes that happen under the loop.

Keep learning!

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *