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

任务

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

问题

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

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

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

解决scheme#1

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

这对在本地安装在node_modules文件夹中的软件包工作正常。 但是,如果我在一个子文件夹中,我需要parsing../node_modules/bootstrap-sass ,并且嵌套更多的文件夹会变得更复杂。 另外,这对全球安装的模块不起作用。

解决scheme#2

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

这将适用于安装在node_modules文件夹中的本地和全局模块。 正则expression式将匹配最后一个node_modulespath元素和下面的path元素。 但是,如果一个包的入口点被设置为另一个包(例如package.json "main": "./node_modules/sub-package" ),则这将失败。

解决scheme#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); 

此function应该适用于安装在node_modules文件夹中的所有软件包。

但…

我想知道这个相当简单的任务是不是可以用一个简单而不那么古怪的方式来解决的。 对我来说,它看起来应该已经构build到Node.js中了。 有什么build议么?

尝试这个:

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

它返回:

 path_to_my_project/node_modules/bootstrap-sass/package.json 

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

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

由于每个软件包都必须包含package.json文件,因此应该始终有效(请参阅什么是软件包? )。

你不需要查找所需的package的package.json文件,甚至不需要明确的引用它的位置。

此外,你不应该这样做,因为没有办法知道npm软件包在npm树中的位置 (这就是为什么你转而求助于require.resolve )。

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

显示可parsing的输出而不是树视图。

例如:

 $ npm ls my-dep -p 

…会输出这样的东西:

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

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

 $ npm ls my-dep -ps 

这个命令可以使用一个subprocess集成到你的代码中 ,在这种情况下,最好运行没有--silent标志的命令来捕获任何错误。

如果出现错误,则可以决定是否致命(例如,上述关于无关包的错误应该被忽略)。

因此通过subprocess使用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 } }); 

这可以在asynchronousstream程中使用(以及一些闭包魔法…),例如:

 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()); }); }; }