Published at
Updated at
Reading time
3min
This post is part of my Today I learned series in which I share all my web development learnings.

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 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 an actual reference to process.mainModule. This is why they are really the same thing. What happens now if we change process.mainModule or require.main?

// test.js
const bar = require('./foo');
console.log(process.mainModule);
console.log(require.main);

// foo.js
// changing both values
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.

Edited:

Alexandre Morgaut pointed out that require.main is part of the CommonJS Spec and that's why it's in Node.js core.

Was this TIL post helpful?
Yes? Cool! You might want to check out Web Weekly for more quick learnings. The last edition went out 13 days ago.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles