检测是否通过require或直接通过命令行调用

我如何检测我的Node.JS文件是否被调用使用SH:node path-to-file或JS:require('path-to-file')?

这是Node.JS等价于我之前在Perl中的问题:我怎么能运行我的Perl脚本,只有当它没有't加载要求?

58936 次浏览
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

还有另一种稍短的方法(没有在提到的文档中列出)。

var runningAsScript = !module.parent;

我在这篇博文中概述了更多关于这一切如何在引子下工作的细节。

我对解释中使用的术语有点困惑。所以我做了几个快速检查。

我发现它们产生了相同的结果:

var isCLI = !module.parent;
var isCLI = require.main === module;

对于其他困惑的人(并直接回答这个问题):

var isCLI = require.main === module;
var wasRequired = !isCLI;

我总是发现自己试图回忆如何编写这个该死的代码片段,所以我决定为它创建一个简单的模块。我花了一点时间让它工作,因为访问调用者的模块信息并不简单,但看到它是如何做到的是很有趣的。

因此,其思想是调用一个模块,并询问调用方模块是否是主模块。我们必须算出调用函数的模块。我的第一个方法是对公认答案的一种变化:

module.exports = function () {
return require.main === module.parent;
};

但这并不一定有效。module.parent指向将我们加载放入内存的模块,而不是调用我们的模块。如果是调用方模块将这个helper模块加载到内存中,那么就没问题。但如果不是,它就不会起作用。所以我们得试试别的办法。我的解决方案是生成一个堆栈跟踪,并从那里获得调用者的模块名:

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");
}

这样更容易记。

如果你正在使用ES6模块,试试这个:

if (process.mainModule.filename === __filename) {
console.log('running as main module')
}

首先,让我们更好地定义问题。我的假设是,你真正要寻找的是你的脚本是否拥有 process.argv(即你的脚本是否负责处理process.argv)。记住这个假设,下面的代码和测试是准确的。

module.parent工作得很好,但它被弃用的原因很充分(一个模块可能有多个父模块,在这种情况下,module.parent只代表第一个父模块),所以使用下面的future-proof条件来涵盖所有情况:

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");
}

它在以下所有情况下的所有脚本中都正确工作,并且永远不会抛出任何错误__abc0:

  • 需要脚本(e.x. require('./main.js'))
  • 直接调用脚本(e.x. nodejs cli.js)
  • 预加载另一个脚本(e.x. nodejs -r main.js cli.js)
  • 连接到节点CLI (e.x. cat cli.js | nodejs)
  • 预加载管道(e.x cat cli.js | nodejs -r main.js)
  • 在工人中(e.x. new Worker('./worker.js'))
  • evaled workers (e.x. new Worker('if (<test for CLI>) ...', {eval: true}))中
  • ES6模块内部(e.x. nodejs --experimental-modules cli-es6.js)
  • 带有预加载的模块(e.x. nodejs --experimental-modules -r main-es6.js cli-es6.js)
  • 管道ES6模块(e.x. cat cli-es6.js | nodejs --experimental-modules)
  • 管道+预加载模块(e.x. cat cli-es6.js | nodejs --experimental-modules -r main-es6.js)
  • 在浏览器中(在这种情况下,CLI为false,因为没有process.argv)
  • 在混合浏览器+服务器环境中(例如ElectronJS,在这种情况下,内联脚本和通过<script>标记加载的所有模块都被认为是CLI)

唯一不工作的情况是当你预加载顶级脚本(e.x. nodejs -r cli.js cli.js)。这个问题不能通过管道(e.x cat cli.js | nodejs -r cli.js)来解决,因为这会执行两次脚本(一次作为必需模块,一次作为顶层)。我不相信有任何可能的解决办法,因为没有办法知道什么主脚本将从一个预加载的脚本。

__理论上,错误可能会从对象的getter内部抛出(例如,如果有人疯狂地执行Object.defineProperty(globalThis, "process", { get(){throw 0} });),然而,对于任何环境中代码片段中使用的属性,在默认情况下都不会发生这种情况。

对于那些使用ES模块(和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.mainmodule.parent__dirname/__filename 在ESM中不可用

请注意:如果使用ESLint,它可能会阻塞在这个语法上,在这种情况下,你需要更新到ESLint ^7.2.0并将你的ecmaVersion调到11 (2020)。

更多信息:process.argvimport.meta.url

我如何检测我的node.js文件是否直接从控制台(windows和unix系统)调用或使用ESM模块导入(import {foo} from 'bar.js')加载

这样的功能没有公开。目前,您应该将cli和库逻辑分离到单独的文件中。

来自node.js核心贡献者devsnek的回答返回nodejs /帮助/问题/ 2420

在我看来这是正确的答案