TIL — how to figure out the Node.js entry script with process.mainModule or require.main

I was reading the import.meta proposal for JavaScript over the weekend. This proposal aims to solve e.g. the issue of accessing module meta information like what the script current element is.

// in Frontend land 
// index.html
<script src="foo.js"></script>
// foo.js 
const currentScript = document.currentScript

This is how you could do it in the browser but how does this work in Node.js? This brings to me learning of the weekend. 🎉

Let’s do a quick refresher first: in Node.js every module and required file is wrapped in a so called module wrapper.

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});

This is were the require function and convenience objects like __filename and __dirname are coming from. In Node.js the is no currentScript but rather you have one entry script which then requires probably thousands of other modules. How could you now figure out if a script is the entry script?

It turns out there are two ways to do this. There is require.main and process.mainModule. So let's have a look what is defined in these two.

// test.js 
console.log(require.main);
console.log(process.mainModule);
// ------------------------------------- 
// output of `$ node test.js`
Module {
id: '.',
exports: {},
parent: null,
filename: '/private/tmp/foo.js',
loaded: false,
children: [],
paths: [
'/private/tmp/node_modules',
'/private/node_modules',
'/node_modules'
]
}
Module {
id: '.',
exports: {},
parent: null,
filename: '/private/tmp/foo.js',
loaded: false,
children: [],
paths: [
'/private/tmp/node_modules',
'/private/node_modules',
'/node_modules'
]
}

Okay… so you can get the filepath of the entry module by accessing require.main.filename or process.mainModule.filename and these two objects also include way more information useful information.

To figure out if a module is the entry script you can then check against the module object.

const isEntryScript = require.main === module;
const isAlsoEntryScript = process.mainModule === module;

But are require.main and process.mainModule actually the same thing?

// test.js 
console.log(require.main === process.mainModule);
// ------------------------------------- 
// output of `$ node test.js`
true

Huh, that’s interesting — they are… So what’s the difference then? The docs are relatively fuzzy on that.

The difference is that if the main module changes at runtime, require.main may still refer to the original main module in modules that were required before the change occurred. Generally, it’s safe to assume that the two refer to the same module.

So what does that mean? I decided to dig the Node.js core code a little bit.

process.mainModule is defined in node/lib/modules.js:

Module._load = function(request, parent, isMain) { 
// ...
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
tryModuleLoad(module, filename);
return module.exports;
};

require.main is defined in node/lib/internals/modules.js:

function makeRequireFunction(mod) { 
// ...
require.main = process.mainModule;
// ...
return require;
}

I didn’t dig the internals any further but the require function that we all use every day holds a reference to process.mainModule. This is way they are really the same thing. What happens now if we change process.mainModule?

// test.js 
const bar = require('./foo');
console.log(process.mainModule);
console.log(require.main);
// foo.js 
// changing both
process.mainModule = 'schnitzel';
require.main = 'pommes';
// ------------------------------------- 
// output of `$ node test.js`
schnitzel
Module {
id: '.',
exports: {},
parent: null,
filename: '/private/tmp/foo.js',
loaded: false,
children:
[
Module {
id: '/private/tmp/bar.js',
exports: {},
parent: [Circular],
filename: '/private/tmp/bar.js',
loaded: true,
children: [],
paths: [Array]
}
],
paths: [
'/private/tmp/node_modules',
'/private/node_modules',
'/node_modules'
]
}

Aha! It turns out that if we set process.mainModule to something else during runtime (I have no idea why I would do this, but yeah ¯_(ツ)_/¯) require.main still holds the reference to the initial main module then.


Originally published at www.stefanjudis.com.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.