Node.js:获取已安装的npm包

时间:2016-01-04 04:44:11

标签: javascript node.js path npm package

任务

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

问题

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

bootstrap-sass为例。假设它已在本地安装在项目文件夹C:\dev\my-project中。然后,我正在寻找的是C:\dev\my-project\node_modules\bootstrap-sassrequire.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路径元素以及以下路径元素的所有内容。但是,如果将包的入口点设置为其他包(例如"main": "./node_modules/sub-package"中的package.json),则会失败。

解决方案#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 个答案:

答案 0 :(得分:8)

试试这个:

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?)。

答案 1 :(得分:2)

您无需查找所需包的package.json文件,甚至无需明确引用其位置。

此外,你不应该这样做,因为没有确定的方法可以知道npm包在npm树的最终位置(这就是你转向require.resolve的原因帮助)。

相反,您可以使用带有npm ls标志的--parseable来查询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标志的命令以允许捕获任何错误。< / p>

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

因此,通过子进程使用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());
        });
    };
}