Detect if called through require or directly by command line
node.jsRequirenode.js Problem Overview
How can I detect whether my Node.JS file was called using SH:node path-to-file
or JS:require('path-to-file')
?
This is the Node.JS equivalent to my previous question in Perl: https://stackoverflow.com/questions/4004466/how-can-i-run-my-perl-script-only-if-it-wasnt-loaded-with-require
node.js Solutions
Solution 1 - node.js
if (require.main === module) {
console.log('called directly');
} else {
console.log('required as a module');
}
See documentation for this here: https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module
Solution 2 - node.js
There is another, slightly shorter way (not outlined in the mentioned docs).
var runningAsScript = !module.parent;
I outlined more details about how this all works under the hood in this blog post.
Solution 3 - node.js
I was a little confused by the terminology used in the explanation(s). So I had to do a couple quick tests.
I found that these produce the same results:
var isCLI = !module.parent;
var isCLI = require.main === module;
And for the other confused people (and to answer the question directly):
var isCLI = require.main === module;
var wasRequired = !isCLI;
Solution 4 - node.js
I always find myself trying to recall how to write this goddamn code snippet, so I decided to create a simple module for it. It took me a bit to make it work since accessing caller's module info is not straightforward, but it was fun to see how it could be done.
So the idea is to call a module and ask it if the caller module is the main one. We have to figure out the module of the caller function. My first approach was a variation of the accepted answer:
module.exports = function () {
return require.main === module.parent;
};
But that is not guaranteed to work. module.parent
points to the module which loaded us into memory, not the one calling us. If it is the caller module that loaded this helper module into memory, we're good. But if it isn't, it won't work. So we need to try something else. My solution was to generate a stack trace and get the caller's module name from there:
module.exports = function () {
// generate a stack trace
const stack = (new Error()).stack;
// the third line refers to our caller
const stackLine = stack.split("\n")[2];
// extract the module name from that line
const callerModuleName = /\((.*):\d+:\d+\)$/.exec(stackLine)[1];
return require.main.filename === callerModuleName;
};
Save this as is-main-module.js
and now you can do:
const isMainModule = require("./is-main-module");
if (isMainModule()) {
console.info("called directly");
} else {
console.info("required as a module");
}
Which is easier to remember.
Solution 5 - node.js
Try this if you are using ES6 modules:
if (process.mainModule.filename === __filename) {
console.log('running as main module')
}
Solution 6 - node.js
For those using ES Modules (and Node 10.12+), you can use import.meta.url
:
import path from 'path';
import { fileURLToPath } from 'url'
const nodePath = path.resolve(process.argv[1]);
const modulePath = path.resolve(fileURLToPath(import.meta.url))
const isRunningDirectlyViaCLI = nodePath === modulePath
Things like require.main
, module.parent
and __dirname
/__filename
aren’t available in ESM.
> Note: If using ESLint it may choke on this syntax, in which case you’ll need to update to ESLint ^7.2.0
and turn your ecmaVersion
up to 11
(2020
).
More info: process.argv
, import.meta.url
Solution 7 - node.js
First, let's define the problem better. My assumption is that what you are really looking for is whether your script owns process.argv
(i.e. whether your script is responsible for processing process.argv
). With this assumption in mind, the code and tests below are accurate.
module.parent
works excellently, but it is deprecated for good reasons (a module might have multiple parents, in which case module.parent
only represents the first parent), so use the following future-proof condition to cover all cases:
if (
typeof process === 'object' && process && process.argv
&& (
(
typeof module === 'object' && module
&& (
!module.parent
|| require.main === module
|| (process.mainModule && process.mainModule.filename === __filename)
|| (__filename === "[stdin]" && __dirname === ".")
)
)
|| (
typeof document === "object"
&& (function() {
var scripts = document.getElementsByTagName("script");
try { // in case we are in a special environment without path
var normalize = require("path").normalize;
for (var i=0,len=scripts.length|0; i < len; i=i+1|0)
if (normalize(scripts[i].src.replace(/^file:/i,"")) === __filename)
return true;
} catch(e) {}
})()
)
)
) {
// this module is top-level and invoked directly by the CLI
console.log("Invoked from CLI");
} else {
console.log("Not invoked from CLI");
}
It works correctly in all of the scripts in all of the following cases and never throws any errors†:
- Requiring the script (e.x.
require('./main.js')
) - Directly invoking the script (e.x.
nodejs cli.js
) - Preloading another script (e.x.
nodejs -r main.js cli.js
) - Piping into node CLI (e.x.
cat cli.js | nodejs
) - Piping with preloading (e.x.
cat cli.js | nodejs -r main.js
) - In workers (e.x.
new Worker('./worker.js')
) - In
eval
ed workers (e.x.new Worker('if (<test for CLI>) ...', {eval: true})
) - Inside ES6 modules (e.x.
nodejs --experimental-modules cli-es6.js
) - Modules with preload (e.x.
nodejs --experimental-modules -r main-es6.js cli-es6.js
) - Piped ES6 modules (e.x.
cat cli-es6.js | nodejs --experimental-modules
) - Pipe+preload module (e.x.
cat cli-es6.js | nodejs --experimental-modules -r main-es6.js
) - In the browser (in which case, CLI is false because there is no
process.argv
) - In mixed browser+server environments (e.x. ElectronJS, in which case both inline scripts and all modules loaded via
<script>
tags are considered CLI)
The only case where is does not work is when you preload the top-level script (e.x. nodejs -r cli.js cli.js
). This problem cannot be solved by piping (e.x. cat cli.js | nodejs -r cli.js
) because that executes the script twice (once as a required module and once as top-level). I do not believe there is any possible fix for this because there is no way to know what the main script will be from inside a preloaded script.
† Theoretically, errors might be thrown from inside of a getter for an object (e.x. if someone were crazy enough to do Object.defineProperty(globalThis, "process", { get(){throw 0} });
), however this will never happen under default circumstances for the properties used in the code snippet in any environment.