【发布时间】:2011-09-17 21:33:27
【问题描述】:
如何检测我的 Node.JS 文件是使用 SH:node path-to-file 还是 JS:require('path-to-file') 调用的?
这是我之前在 Perl 中提出的问题的 Node.JS:How can I run my Perl script only if it wasn't loaded with require?
【问题讨论】:
如何检测我的 Node.JS 文件是使用 SH:node path-to-file 还是 JS:require('path-to-file') 调用的?
这是我之前在 Perl 中提出的问题的 Node.JS:How can I run my Perl script only if it wasn't loaded with require?
【问题讨论】:
对于那些使用 ES Modules(和 Node 10.12+)的人,你可以使用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
require.main、module.parent 和 __dirname/__filenamearen’t available in ESM 之类的东西。
注意:如果使用 ESLint,它可能会阻塞这种语法,在这种情况下,您需要 update to ESLint
^7.2.0并将您的ecmaVersion转换为11(2020)。
更多信息:process.argv, import.meta.url
【讨论】:
fileURLtoPath,而是使用path模块比较resolved路径,这样就不会遇到符号链接问题。
首先,让我们更好地定义问题。我的假设是,您真正要寻找的是您的脚本是否拥有 process.argv(即您的脚本是否负责处理process.argv)。考虑到这一假设,下面的代码和测试是准确的。
module.parent 工作得很好,但有充分的理由不推荐使用它(一个模块可能有多个父级,在这种情况下module.parent 仅代表第一个父级),因此使用以下面向未来的条件来涵盖所有情况:
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");
}
它在以下所有情况下的所有脚本中都能正常工作,并且从不抛出任何错误†:
require('./main.js'))nodejs cli.js)nodejs -r main.js cli.js)cat cli.js | nodejs)cat cli.js | nodejs -r main.js)new Worker('./worker.js'))evaled 工作人员中(例如new Worker('if (<test for CLI>) ...', {eval: true}))nodejs --experimental-modules cli-es6.js)nodejs --experimental-modules -r main-es6.js cli-es6.js)cat cli-es6.js | nodejs --experimental-modules)cat cli-es6.js | nodejs --experimental-modules -r main-es6.js)process.argv)<script> 标签加载的模块都被视为 CLI)唯一不起作用的情况是当您预加载顶级脚本时(例如nodejs -r cli.js cli.js)。这个问题不能通过管道解决(例如cat cli.js | nodejs -r cli.js),因为它会执行脚本两次(一次作为必需模块,一次作为顶层)。我不相信有任何可能的解决方法,因为没有办法知道预加载脚本中的主脚本是什么。
† 从理论上讲,错误可能会从对象的 getter 内部抛出(例如,如果有人疯狂到做到Object.defineProperty(globalThis, "process", { get(){throw 0} });),但是在属性的默认情况下这永远不会发生用于任何环境下的代码sn-p。
【讨论】:
module 或 document 均未定义为全局变量)。您可以在目录中使用"type": "module" package.json、包含您的代码的test.js 文件验证这一点,然后运行node test.js。它会错误地报告Not invoked from CLI。
我总是发现自己试图回忆如何编写这个该死的代码 sn-p,所以我决定为它创建一个简单的模块。由于访问调用者的模块信息并不简单,我花了一点时间才使它工作,但看到它是如何完成的很有趣。
所以想法是调用一个模块并询问调用者模块是否是主模块。我们必须弄清楚调用者函数的模块。我的第一种方法是接受答案的变体:
module.exports = function () {
return require.main === module.parent;
};
但这不能保证有效。 module.parent 指向将我们加载到内存中的模块,而不是调用我们的模块。如果是调用者模块将此帮助模块加载到内存中,我们很好。但如果不是,它就行不通。所以我们需要尝试其他的东西。我的解决方案是生成堆栈跟踪并从那里获取调用者的模块名称:
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;
};
将其保存为is-main-module.js,现在您可以这样做了:
const isMainModule = require("./is-main-module");
if (isMainModule()) {
console.info("called directly");
} else {
console.info("required as a module");
}
哪个更容易记住。
【讨论】:
return require.main /*this is undefined if we started node interactively*/ && require.main.filename === callerModuleName;
if (require.main === module) {
console.log('called directly');
} else {
console.log('required as a module');
}
在此处查看相关文档:https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module
【讨论】:
require() 执行此操作,但您可以通过导入文件然后在其上运行eval 或运行require('child_process').exec('node the_file.js') 来完成此操作跨度>
es-main 包来检查模块是否直接运行。
如果你使用的是 ES6 模块,试试这个:
if (process.mainModule.filename === __filename) {
console.log('running as main module')
}
【讨论】:
process.mainModule 是undefined
if (require.main?.filename === __filename) {...}
process.mainModule 是 undefined。真正的答案在这里:stackoverflow.com/a/66309132/575796
我对解释中使用的术语有些困惑。所以我不得不做几个快速测试。
我发现这些会产生相同的结果:
var isCLI = !module.parent;
var isCLI = require.main === module;
对于其他困惑的人(并直接回答问题):
var isCLI = require.main === module;
var wasRequired = !isCLI;
【讨论】:
【讨论】:
node script.js 但不是cat script.js | node。这种方式对双方都有效。