在Node中检测ES模块是否从命令行运行

28

在Node使用CommonJS模块时,可以使用require.main === module来检测脚本是否从命令行运行。

当使用Node的ES模块(带有--experimental-modules标志)时,检测脚本是否从命令行运行的等效方法是什么?

6个回答

29

使用

if (import.meta.url === `file://${process.argv[1]}`) {
  // module was not imported but called directly
}

详细信息请参阅MDN文档中的import.meta

2021年9月27日更新

可能更加健壮,但需要通过额外导入(通过Rich Harris)实现。

import { pathToFileURL } from 'url'

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  // module was not imported but called directly
}

8
其他答案离答案很接近,但对于一个相当典型的用例——在package.json文件中通过bin属性公开的cli脚本,会有所疏漏。
这些脚本将在node_modules/.bin文件夹中创建符号链接。可以通过npx或作为package.jsonscripts对象定义的脚本来调用这些脚本。那种情况下,process.argv[1]是符号链接而不是import.meta.url引用的实际文件。
此外,我们需要将文件路径转换为实际的file:// url,否则在不同平台上它将无法正常工作。
import { realpathSync } from "fs";
import { pathToFileURL } from "url";

function wasCalledAsScript() {
    // We use realpathSync to resolve symlinks, as cli scripts will often
    // be executed from symlinks in the `node_modules/.bin`-folder
    const realPath = realpathSync(process.argv[1]);

    // Convert the file-path to a file-url before comparing it
    const realPathAsUrl = pathToFileURL(realPath).href;
  
    return import.meta.url === realPathAsUrl;
}

if (wasCalledAsScript()) {
  // module was executed and not imported by another file.
}

我本来想将这个发表为对已被接受的答案的评论,但显然我的新账户不允许进行评论。


这应该被接受的答案。 - morganney
这应该是被接受的答案。 - undefined

6
目前还没有(它仍处于试验阶段!)。尽管普遍认为这样的检查本身就是一种不好的实践,你应该为库和可执行文件提供单独的脚本,但有一个想法为此提供了一个布尔值 import.meta.main 属性。 传送门

现在怎么样?这还是不可能的吗? - Janosh
2
这段代码看起来是正常工作的:if (import.meta.url === `file://${process.argv[1]}`) // module was not imported but called directly - Janosh
@Casimir 请查看链接的节点问题线程。虽然它还没有被实现,但有一些解决方法。 - Bergi
1
@Casimir 或者说,import.meta.url === url.pathToFileURL(process.argv[1])(来自 rich harris 在 Twitter 上的消息) - forivall
@forivall 谢谢,我已经更新了我的答案。 - Janosh
显示剩余2条评论

1
在CommonJS中,将定义全局变量module,但在ES模块中根本不存在。是的,这里存在不一致之处,即ES模块是没有module变量的东西。
您可以通过检查typeof v是否为字符串(而非值!)'undefined'来检查未定义的变量。
这变成了:
const inCommonJs = typeof module !== 'undefined';

console.log(`inCommonJs = ${inCommonJs}`);

如果我们将该代码粘贴到.cjs.mjs文件中,我们将得到正确的答案:
$ node foo.mjs
inCommonJs = false
$ cp foo.mjs foo.cjs
$ node foo.cjs
inCommonJs = true

1
不确定这有什么帮助...问题是如何在mjs中检测我们是从cli调用还是从另一个模块导入。 - nic ferrier

0

我喜欢 import.meta.url === `file://${process.argv[1]}` ,但在 Windows 的 bash shell 中无法正常工作。这是一个替代方案,只检查基本名称:

const runningAsScript = import.meta.url.endsWith(path.basename(process.argv[1]));

-4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接