【问题标题】:Detect if called through require or directly by command line检测是通过require调用还是直接通过命令行调用
【发布时间】: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 require


【解决方案1】:

对于那些使用 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.mainmodule.parent__dirname/__filenamearen’t available in ESM 之类的东西。

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

更多信息:process.argv, import.meta.url

【讨论】:

  • 如果您的工作目录是符号链接,则会中断。例如。在 Mac OS 上,/tmp -> /private/tmp。如果你 cd /tmp 并在那里运行脚本,meta.url = file:///private/tmp/...,而 process.argv[1] = /tmp/...。
  • 不要直接检查fileURLtoPath,而是使用path模块比较resolved路径,这样就不会遇到符号链接问题。
【解决方案2】:

首先,让我们更好地定义问题。我的假设是,您真正要寻找的是您的脚本是否拥有 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')
  • evaled 工作人员中(例如new Worker('if (&lt;test for CLI&gt;) ...', {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,在这种情况下内联脚本和所有通过 &lt;script&gt; 标签加载的模块都被视为 CLI)

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

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

【讨论】:

  • 这不适用于 es 模块(moduledocument 均未定义为全局变量)。您可以在目录中使用"type": "module" package.json、包含您的代码的test.js 文件验证这一点,然后运行node test.js。它会错误地报告Not invoked from CLI
【解决方案3】:

我总是发现自己试图回忆如何编写这个该死的代码 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");
}

哪个更容易记住。

【讨论】:

  • 非常酷。当通用代码 sn-ps 缩写为单个名称时,我喜欢它。小调整:return require.main /*this is undefined if we started node interactively*/ &amp;&amp; require.main.filename === callerModuleName;
【解决方案4】:
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() 它并让它像直接调用它一样工作。基本上,我需要欺骗使用该测试的东西认为它是直接调用的。
  • @Kevin 我不知道如何使用require() 执行此操作,但您可以通过导入文件然后在其上运行eval 或运行require('child_process').exec('node the_file.js') 来完成此操作跨度>
  • 在使用 ES 模块和 Node.js 时,可以使用 es-main 包来检查模块是否直接运行。
  • 我后悔使用 ES 模块。所有的教程都是在它们存在之前编写的,有很多东西不起作用,现在我必须安装 npm 包,这样我就可以从脚本中导入有用的函数而不执行实际的脚本??
【解决方案5】:

如果你使用的是 ES6 模块,试试这个:

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

【讨论】:

  • 废话,我的process.mainModuleundefined
  • GHOSHHHH,我需要在我的 .mjs 文件中检查这个
  • 与 node14 if (require.main?.filename === __filename) {...}
  • __filename 在 ES 模块中已弃用:nodejs.org/docs/latest-v12.x/api/… 请参阅文档以获取替代方案
  • 否决这个 process.mainModuleundefined。真正的答案在这里:stackoverflow.com/a/66309132/575796
【解决方案6】:

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

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

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

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

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

【讨论】:

    【解决方案7】:

    还有另一种稍短的方法(未在提到的文档中概述)。

    var runningAsScript = !module.parent;

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

    【讨论】:

    • +1,我更喜欢这个,但在切换接受的答案之前我会犹豫。 :)
    • 正如我所指出的,记录在案的官方方式是@nicolaskruchten 概述的方式。这只是一种选择,无需切换已接受的答案。两者都有效。
    • 我不得不使用这个而不是记录的方式——记录的方式适用于例如。 node script.js 但不是cat script.js | node。这种方式对双方都有效。
    猜你喜欢
    • 1970-01-01
    • 2012-08-26
    • 1970-01-01
    • 1970-01-01
    • 2020-01-05
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多