首页 文章

Node.js:获取已安装的npm包的(绝对)根路径

提问于
浏览
7

任务

我正在寻找一种通用方法来获取Node.js中已安装的npm包的(绝对)根路径 .

问题

我知道 require.resolve ,但这会给我一个入口点(主模块的路径)而不是包的根路径 .

bootstrap-sass 为例 . 假设它在本地安装在项目文件夹 C:\dev\my-project 中 . 然后我要找的是 C:\dev\my-project\node_modules\bootstrap-sass . require.resolve('bootstrap-sass') 将返回 C:\dev\my-project\node_modules\bootstrap-sass\assets\javascripts\bootstrap.js .

我可以想到几种方法如何获取包的根路径:

解决方案#1

var packageRoot = path.resolve('node_modules/bootstrap-sass');
console.log(packageRoot);

这适用于在 node_modules 文件夹中本地安装的软件包 . 但是,如果我在子文件夹中,我需要解决 ../node_modules/bootstrap-sass ,并且使用更多嵌套文件夹会更复杂 . 此外,这不适用于全局安装的模块 .

解决方案#2

var packageRoot = require.resolve('bootstrap-sass')
    .match(/^.*[\/\\]node_modules[\/\\][^\/\\]*/)[0];
console.log(packageRoot);

这适用于安装在 node_modules 文件夹中的本地和全局模块 . 正则表达式将匹配最后一个 node_modules 路径元素以及以下路径元素的所有内容 . 但是,如果将包的入口点设置为另一个包(例如 package.json 中的 "main": "./node_modules/sub-package" ),则此操作将失败 .

解决方案#3

var escapeStringRegexp = require('escape-string-regexp');

/**
 * Get the root path of a npm package installed in node_modules.
 * @param {string} packageName The name of the package.
 * @returns {string} Root path of the package without trailing slash.
 * @throws Will throw an error if the package root path cannot be resolved
 */
function packageRootPath(packageName) {
    var mainModulePath = require.resolve(packageName);
    var escapedPackageName = escapeStringRegexp(packageName);
    var regexpStr = '^.*[\\/\\\\]node_modules[\\/\\\\]' + escapedPackageName +
        '(?=[\\/\\\\])';
    var rootPath = mainModulePath.match(regexpStr);
    if (rootPath) {
        return rootPath[0];
    } else {
        var msg = 'Could not resolve package root path for package `' +
            packageName + '`.'
        throw new Error(msg);
    }
}

var packageRoot = packageRootPath('bootstrap-sass');
console.log(packageRoot);

此功能适用于安装在 node_modules 文件夹中的所有软件包 .

但......

我想知道这个相当简单的任务是否不能以更简单,更少的黑客方式解决 . 对我而言,它看起来应该已经构建到Node.js中 . 有什么建议?

2 回答

  • 8

    您不需要查找所需包的package.json文件,甚至不需要显式引用它的位置 .

    此外,你不应该这样做,因为没有确定的方法知道npm包将在npm的树中结束(这就是为什么你转向 require.resolve 寻求帮助) .

    相反,您可以使用带有--parseable标志的npm ls查询npm API(或CLI),它将:

    显示可解析的输出而不是树视图 .

    例如:

    $ npm ls my-dep -p
    

    ...将输出如下内容:

    /Users/me/my-project/node_modules/my-dep
    

    你应该知道这个命令可以输出一些不相关的错误以及stderr(例如关于无关安装) - 要解决这个问题,激活 --silent 标志(参见文档中的loglevel):

    $ npm ls my-dep -ps
    

    可以使用child process将此命令集成到您的代码中,在这种情况下,首选运行不带 --silent 标志的命令以允许捕获任何错误 .

    如果捕获了错误,则可以判断其是否致命(例如上述关于无关包的错误应该被忽略) .

    因此,通过子进程使用CLI可能如下所示:

    const exec = require('child_process').exec;
    const packageName = 'my-dep';
    
    exec(`npm ls ${packageName} --parseable`, (err, stdout, stderr) => {
        if (!err) {
            console.log(stdout.trim()); // -> /Users/me/my-project/node_modules/my-dep
        }
    });
    

    然后可以在异步流中使用它(以及一些封闭魔法......),例如:

    const exec = require('child_process').exec;
    const async = require('async');
    
    async.waterfall([
        npmPackagePathResolver('my-dep'),
        (packagePath, callback) => {
            console.log(packagePath) // -> /Users/me/my-project/node_modules/my-dep
            callback();
        }
    ], (err, results) => console.log('all done!'));
    
    function npmPackagePathResolver(packageName) {
        return (callback) => {
            exec(`npm ls ${packageName} --parseable`, (err, stdout, stderr) => {
                callback(err, stdout.trim());
            });
        };
    }
    
  • 2

    试试这个:

    require.resolve('bootstrap-sass/package.json')
    

    返回:

    path_to_my_project/node_modules/bootstrap-sass/package.json
    

    你现在可以摆脱'package.json'路径后缀,例如:

    var path = require('path') // npm install path
    var bootstrapPath = path.dirname(require.resolve('bootstrap-sass/package.json'))
    

    由于每个包都必须包含 package.json 文件,因此应始终有效(请参阅What is a package?) .

相关问题