Node.js - 检查模块是否安装,而不实际要求它

57

在运行 "mocha" 之前,我需要检查它是否已经安装。我想到了以下代码:

try {
    var mocha = require("mocha");
} catch(e) {
    console.error(e.message);
    console.error("Mocha is probably not found. Try running `npm install mocha`.");
    process.exit(e.code);
}

我不喜欢使用捕获异常的方法,有更好的方式吗?


2
我已经回答了,但现在我注意到了“全局”这个词。你所说的“全局”,是指使用npm安装时带有-g选项的模块吗?例如:npm install -g mocha?编辑:据我所知,require无法找到使用-g选项安装的模块。 - Jan Święcki
2个回答

95

你应该使用require.resolve()而不是require()require会在找到库时加载它,但require.resolve()不会加载,它将返回模块的文件名。

请参阅require.resolve的文档

try {
    console.log(require.resolve("mocha"));
} catch(e) {
    console.error("Mocha is not found");
    process.exit(e.code);
}

如果模块未被找到,require.resolve()会抛出错误,因此您必须处理它。


14
"require.resolve" 仍会抛出错误 - 这就是我试图避免的 - 我要捕获异常。 - AndreyM
3
由于其他解决方案在任何方面都不更加简洁,因此接受这个答案。 - AndreyM
是的,但这也会查找全局安装的模块吗?我想检查一个模块是否在本地安装。 - Alexander Mills
@AlexMills 更好的方法是使用 npm ls,其中 global 默认为 false。 - user568109

3

module.paths 存储了 require 的搜索路径数组。这些路径是相对于调用 require 的当前模块的路径。因此:

var fs = require("fs");

// checks if module is available to load
var isModuleAvailableSync = function(moduleName)
{
    var ret = false; // return value, boolean
    var dirSeparator = require("path").sep

    // scan each module.paths. If there exists
    // node_modules/moduleName then
    // return true. Otherwise return false.
    module.paths.forEach(function(nodeModulesPath)
    {
        if(fs.existsSync(nodeModulesPath + dirSeparator + moduleName) === true)
        {
            ret = true;
            return false; // break forEach
        }
    });

    return ret;
}

并且异步版本:

// asynchronous version, calls callback(true) on success
// or callback(false) on failure.
var isModuleAvailable = function(moduleName, callback)
{
    var counter = 0;
    var dirSeparator = require("path").sep

    module.paths.forEach(function(nodeModulesPath)
    {
        var path = nodeModulesPath + dirSeparator + moduleName;
        fs.exists(path, function(exists)
        {
            if(exists)
            {
                callback(true);
            }
            else
            {
                counter++;

                if(counter === module.paths.length)
                {
                    callback(false);
                }
            }
        });
    });
};

使用方法:

if( isModuleAvailableSync("mocha") === true )
{
    console.log("yay!");
}

或者:

isModuleAvailable("colors", function(exists)
{
    if(exists)
    {
        console.log("yay!");
    }
    else
    {
        console.log("nay:(");
    }
});

编辑:注意:

  • module.paths不在API中。
  • 文档说明可以添加路径,这些路径将被require扫描,但我无法使其工作(我使用的是Windows XP)。

@AndreyM,只是好奇想知道您不喜欢这个解决方案的原因,以及为什么您标记了接受的答案,其中使用try/catch hack来确定模块是否存在?谢谢。 - jamesmortensen
我认为因为 module.paths 不在 API 中,所以你没有选择这个方法。如果 Node 以某种方式发生变化,我可以理解它可能不会是未来可靠的,而 try/catch 的策略虽然丑陋,但更加可靠。尽管如此,这个答案很棒,解决了这个问题而不需要使用 try/catch!+1 :) - jamesmortensen

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