Node.JS:检测是否通过require调用或直接通过命令行调用

时间:2011-06-18 18:56:08

标签: node.js 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?

7 个答案:

答案 0 :(得分:388)

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

答案 1 :(得分:76)

还有另一种略短的方式(未在上述文档中概述)。

var runningAsScript = !module.parent;

我在this blog post中概述了有关这一切是如何运作的更多细节。

答案 2 :(得分:4)

我对解释中使用的术语感到有些困惑。所以我不得不做几次快速测试。

我发现这些产生了相同的结果:

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

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

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

答案 3 :(得分:2)

就像在Python中一样,我总是发现自己试图记住如何编写这个该死的代码片段。所以我决定为它创建一个简单的模块。我花了一些时间来开发,因为访问调用者的模块信息并不简单,但看看它是如何完成的很有趣。

因此,我们的想法是调用一个模块并询问它是否是主调用模块。我们必须弄清楚调用函数的模块。我的第一种方法是接受答案的变体:

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

现在我们可以做到:

if (require("./is-main-module")()) {  // notice the `()` at the end
    // do something
} else {
    // do something else
}

或更具可读性:

const isMainModule = require("./is-main-module");

if (isMainModule()) {
    // do something
} else {
    // do something else
}

不可能忘记: - )

答案 4 :(得分:2)

如果使用的是ES6模块,请尝试以下操作:

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

答案 5 :(得分:2)

对于使用 ES 模块(和 Node 10.12+)的用户,您可以使用 import.meta.url

import { fileURLToPath } from 'url'

const isRunningDirectlyViaCLI = process.argv[1] === fileURLToPath(import.meta.url)

诸如 require.mainmodule.parent__dirname/__filename aren’t available in ESM 之类的东西。

<块引用>

注意:如果使用 ESLint,它可能会阻塞这种语法,在这种情况下,您需要 update to ESLint ^7.2.0 并将 ecmaVersion 变为 11 (2020)。

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

答案 6 :(得分:1)

首先,让我们更好地定义问题。我的假设是,您真正要寻找的是您的脚本是否拥有 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
  • 管道到节点 CLI(例如 cat cli.js | nodejs
  • 预加载管道(例如 cat cli.js | nodejs -r main.js
  • 在工人中(例如 new Worker('./worker.js')
  • eval教育工作者(例如new Worker('if (<test for CLI>) ...', {eval: true})
  • 在 ES6 模块内部(例如 nodejs --experimental-modules cli-es6.js
  • 具有预加载的模块(例如 nodejs --experimental-modules -r main-es6.js cli-es6.js
  • 管道 ES6 模块(例如 cat cli-es6.js | nodejs --experimental-modules
  • 管道+预加载模块(例如cat cli-es6.js | nodejs --experimental-modules -r main-es6.js
  • 在浏览器中(在这种情况下,CLI 为 false,因为没有 process.argv
  • 在浏览器+服务器的混合环境中(例如 ElectronJS,在这种情况下,内联脚本和通过 <script> 标签加载的所有模块都被视为 CLI)

is 不起作用的唯一情况是您预加载顶级脚本(例如 nodejs -r cli.js cli.js)。这个问题不能通过管道(例如 cat cli.js | nodejs -r cli.js)来解决,因为它会执行脚本两次(一次作为必需的模块,一次作为顶层)。我不相信有任何可能的解决方法,因为无法从预加载的脚本中知道主脚本是什么。

从理论上讲,错误可能会从对象的 getter 内部抛出(例如,如果有人疯狂到做 Object.defineProperty(globalThis, "process", { get(){throw 0} });),但是这在默认情况下永远不会发生在任何环境中的代码片段中使用的属性。