我正在寻找一种通用方法来获取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
。
我可以想到几种方法如何获得包的根路径:
var packageRoot = path.resolve('node_modules/bootstrap-sass');
console.log(packageRoot);
这适用于node_modules
文件夹中本地安装的软件包。但是,如果我在子文件夹中,我需要解析../node_modules/bootstrap-sass
,并且使用更多嵌套文件夹会更复杂。此外,这不适用于全局安装的模块。
var packageRoot = require.resolve('bootstrap-sass')
.match(/^.*[\/\\]node_modules[\/\\][^\/\\]*/)[0];
console.log(packageRoot);
这适用于安装在node_modules
文件夹中的本地和全局模块。正则表达式将匹配最后一个node_modules
路径元素以及以下路径元素的所有内容。但是,如果将包的入口点设置为其他包(例如"main": "./node_modules/sub-package"
中的package.json
),则会失败。
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中。有什么建议吗?
答案 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());
});
};
}