Jak zjistit vstupní skript Node.js s process.mainModule nebo require.main

O víkendu jsem četl návrh import.meta pro JavaScript. Tento návrh má za cíl vyřešit např. problém přístupu k metainformacím modulu, například co je aktuální prvek skriptu.

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

// foo.js
const currentScript = document.currentScript

Takto byste to mohli udělat v prohlížeči, ale jak to funguje v Node.js? To mi přináší učení o víkendu. 🎉

Udělejme si nejprve rychlé osvěžení:v Node.js je každý modul a požadovaný soubor zabalen do tzv. modulového obalu.

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

Toto jsou require funkční a komfortní objekty jako __filename a __dirname pocházejí z. V Node.js není currentScript ale spíše máte jeden vstupní skript, který pak vyžaduje pravděpodobně tisíce dalších modulů. Jak byste nyní mohli zjistit, zda je skript vstupním skriptem?

Ukazuje se, že existují dva způsoby, jak toho dosáhnout. Existuje require.main a process.mainModule . Pojďme se tedy podívat, co je definováno v těchto dvou.

// 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' ] }

Dobře... takže cestu k souboru vstupního modulu můžete získat z require.main.filename nebo process.mainModule.filename a tyto dva objekty také obsahují mnohem užitečnější informace.

Chcete-li zjistit, zda je modul vstupním skriptem, můžete jej zkontrolovat podle module objekt.

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

Ale jsou require.main a process.mainModule vlastně to samé?

// test.js
console.log(require.main === process.mainModule);

// -------------------------------------
// output of `$ node test.js`
true

Huh, to je zajímavé – jsou... Tak jaký je v tom tedy rozdíl? Dokumenty jsou v tom poměrně nejasné.

Co to tedy znamená? Rozhodl jsem se trochu překopat základní kód Node.js.

process.mainModule je definován v 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 je definován v node/lib/internals/modules.js :

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

Nekopal jsem dále do vnitřních částí, ale do require funkce, kterou všichni používáme každý den, obsahuje skutečný odkaz na process.mainModule . To je důvod, proč jsou skutečně stejné. Co se stane, když změníme process.mainModule nebo 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! Ukazuje se, že pokud nastavíme process.mainModule na něco jiného během běhu (nemám ponětí, proč bych to dělal, ale ano ¯_(ツ)_/¯) require.main pak stále obsahuje odkaz na počáteční hlavní modul.

Upraveno:

Alexandre Morgaut poukázal na to, že require.main je součástí specifikace CommonJS, a proto je součástí jádra Node.js.