【问题标题】:Node.js: Get (absolute) root path of installed npm packageNode.js:获取已安装 npm 包的(绝对)根路径
【发布时间】:2016-04-07 16:34:53
【问题描述】:

任务

我正在寻找一种通用方法来获取 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 路径元素加上以下路径元素的所有内容。但是,如果一个包的入口点设置为另一个包(例如,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 中了。有什么建议吗?

【问题讨论】:

  • 您真正想解决什么问题(例如,您为什么要尝试这条路径)?您是否正在尝试获取模块中其他资源的路径?通常一个模块本身应该知道它自己的资源相对于它的位置在哪里,所以你只需要让模块告诉你东西在哪里,它就会给你答案。
  • 我猜第一个问题是你为什么要查找模块的位置而不是它的主文件
  • 其次,npm 有几个模块来提供它的功能,包括查找 node_modules 的目录,例如 read-package-tree
  • 我想用 npm 安装 css 框架(例如 bootstrap)并获取包含文件的路径(例如 .css/.scss/.svg/...)。
  • read-package-tree 需要一个包根路径作为它的第一个参数...

标签: javascript node.js path npm package


【解决方案1】:

试试这个:

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

【讨论】:

  • 但是,请记住,您要实现的目标至少有点骇人听闻(尽管这可能是最合理的做法)。
  • 谢谢! :) 目前这似乎是最好的解决方案。使用 npm 安装的软件包将始终有一个 package.json 文件(请参阅What is a package?)。因此,path.dirname(require.resolve('bootstrap-sass/package.json')) 可以解决问题。
  • 好的,谢谢。我根据您的评论编辑了答案,使其更加完整和独立。如果这对你有用,你能接受答案吗?谢谢。
【解决方案2】:

您无需查找所需包的 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());
        });
    };
}

【讨论】:

  • 实际上,即使有无关的软件包,我也没有看到任何关于无关软件包的输出......也许它们改变了行为,或者这些错误仅在您使用 npm ls 而没有指定时发生包名,即 npm ls --parseable 而不是 npm ls some-package --parseable
  • @MattBrowne 正如你所怀疑的,我认为这与撰写本文时的 npm 版本有关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多