为啥在 npm 中为插件使用对等依赖项?

Posted

技术标签:

【中文标题】为啥在 npm 中为插件使用对等依赖项?【英文标题】:Why use peer dependencies in npm for plugins?为什么在 npm 中为插件使用对等依赖项? 【发布时间】:2014-12-31 12:01:25 【问题描述】:

例如,为什么 Grunt 插件将其对 grunt 的依赖定义为“peer dependencies”?

为什么插件不能在 grunt-plug/node_modules 中将 Grunt 作为自己的依赖项?

此处描述了对等依赖项:https://nodejs.org/en/blog/npm/peer-dependencies/

但我真的不明白。

示例

我目前正在使用 AppGyver Steroids,它使用 Grunt 任务将我的源文件构建到 /dist/ 文件夹中,以便在本地设备上提供服务。我在 npm 和 grunt 上很新,所以我想完全理解发生了什么。

到目前为止,我得到了这个:

[rootfolder]/package.json 告诉 npm 它依赖于 grunt-steroids npm 包进行开发:

  "devDependencies": 
    "grunt-steroids": "0.x"
  ,

好的。在 [rootfolder] 中运行 npm install 会检测到依赖关系并在 [rootfolder]/node_modules/grunt-steroids 中安装 grunt-steroids。

Npm 然后读取 [rootfolder]/node_modules/grunt-steroids/package.json 以便它可以安装 grunt-steroids 自己的依赖项。:

"devDependencies": 
    "grunt-contrib-nodeunit": "0.3.0",
    "grunt": "0.4.4"
  ,
"dependencies": 
    "wrench": "1.5.4",
    "chalk": "0.3.0",
    "xml2js": "0.4.1",
    "lodash": "2.4.1"
  ,
"peerDependencies": 
    "grunt": "0.4.4",
    "grunt-contrib-copy": "0.5.0",
    "grunt-contrib-clean": "0.5.0",
    "grunt-contrib-concat": "0.4.0",
    "grunt-contrib-coffee": "0.10.1",
    "grunt-contrib-sass": "0.7.3",
    "grunt-extend-config": "0.9.2"
  ,

dependencies”软件包安装到 [rootfolder]/node_modules/grunt-steroids/node_modules 中,这对我来说是合乎逻辑的。

没有安装“devDependencies”,我确定它是由 npm 控制的,检测我只是想使用grunt-steroids,而不是在上面开发。

但是我们有“peerDependencies”。

这些安装在 [rootfolder]/node_modules 中,我不明白为什么没有安装在 [rootfolder]/node_modules/grunt-steroids/node_modules 中所以避免与其他 grunt 插件(或其他)冲突?

【问题讨论】:

【参考方案1】:

我建议你先再读一遍这篇文章。这有点令人困惑,但使用 winston-mail 的示例向您展示了原因:

例如,假设winston-mail@0.2.3 在其"dependencies" 对象中指定了"winston": "0.5.x",因为这是对其进行测试的最新版本。作为应用程序开发人员,您想要最新最好的东西,因此您查找winstonwinston-mail 的最新版本并将它们作为


  "dependencies":   
    "winston": "0.6.2",  
    "winston-mail": "0.2.3"  
    

但是现在,运行 npm install 会导致意外的依赖关系图

├── winston@0.6.2  
└─┬ winston-mail@0.2.3                
  └── winston@0.5.11

在这种情况下,一个包可能有多个版本,这会导致一些问题。对等依赖项允许 npm 开发人员确保用户具有特定模块(在根文件夹中)。但是你说得对,描述一个特定版本的包会导致使用其他版本的其他包出现问题。正如文章所述,此问题与 npm 开发人员有关

一条建议:对等依赖要求,与常规依赖不同,应该宽松。您不应该将您的对等依赖项锁定到特定的补丁版本。

因此,开发人员应遵循semver 来定义 peerDependencies。你应该在 GitHub 上为 grunt-steroids 包打开一个问题...

【讨论】:

你说multiple versions of a package which would cause some issues 但这不是包管理器的重点吗?他们甚至在同一篇文章中进一步讨论了这一点,其中项目中有同一个包的 2 个版本:一个由开发人员提供,一个由 3rd 方库提供。 我想我理解对等依赖的意义,但在winston 示例中,我现在是否无法使用winston-mail 库,因为我的版本与对等依赖不匹配?我宁愿将 1 库的最新和最好的临时降级,也不愿根本无法使用它。 对于您的第一条评论,据我了解和使用它,它与测试有关,例如如果您有一个已针对特定 3rd 方包进行测试的包,则您无法确定如果您的依赖项之一发生更改(错误修复、主要功能更新),您的包是否可以正常工作。因此,您可以指定特定的插件版本并与您的测试一起保存。 关于您的第二条评论:这就是为什么他们在文档中说开发人员应该对他们的包依赖项宽容并应该使用 semver,例如而不是 "0.2.1","~0.2.1"-> 允许 "0.2.x" 但不允许 "0.3.x" ,或 ">=0.2.1" -> 从 "0.2.x" 到 "1" 的所有内容.x”或“x.2.”。 ..(但对于一个 npm 包来说并不是真的更可取~【参考方案2】:

TL;DR: peerDependencies 用于暴露给消费代码(并预期被消费代码使用)的依赖项,而不是 “私有” 依赖项没有公开,只是一个实现细节。

对等依赖解决的问题

NPM 的模块系统是分层的。更简单场景的一大优势是,当您安装 npm 包时,该包会自带依赖项,因此它可以开箱即用。

但在以下情况下会出现问题:

您的项目和您正在使用的某个模块都依赖于另一个模块。 三个模块必须相互通信。

在示例中

假设您正在构建YourCoolProject,并且同时使用JacksModule 1.0JillsModule 2.0。假设JacksModule 也依赖于JillsModule,但依赖于不同的版本,比如1.0。只要这2个版本不满足,就没有问题。 JacksModule 在表面之下使用JillsModule 的事实只是一个实现细节。我们将 JillsModule 捆绑了两次,但是当我们获得开箱即用的稳定软件时,这是一个很小的代价。

但是现在如果JacksModule 以某种方式暴露了它对JillsModule 的依赖呢?例如,它接受JillsClass 的实例...当我们使用库的版本2.0 创建new JillsClass 并将其传递给jacksFunction 时会发生什么?所有的地狱都会崩溃!像jillsObject instanceof JillsClass 这样简单的事情会突然返回false,因为jillsObject 实际上是另一个 JillsClass 的实例,2.0 版本。

对等依赖如何解决这个问题

他们告诉 npm

我需要这个包,但我需要它的一部分的版本 项目,而不是我的模块私有的某个版本。

当 npm 发现你的包被安装到一个没有有该依赖的项目中,或者有一个不兼容的版本的项目中,它会警告用户在安装过程中。

什么时候应该使用对等依赖项?

当您构建一个供其他项目使用的库时, 此库正在使用其他库, 您希望/需要用户也使用其他库

常见的场景是大型框架的插件。想想 Gulp、Grunt、Babel、Mocha 等。如果您编写 Gulp 插件,您希望该插件与用户项目正在使用的 Gulp 一起使用,而不是与您自己的 Gulp 私有版本一起使用。

【讨论】:

我注意到并且没有在任何地方说的一件重要的事情,当我们构建一个插件时,我们是否应该有一个包依赖项的副本,用于对等依赖项?在 OP 示例中,我们可以看到 "grunt": "0.4.4" 位于 devDependencies 和 peerDependencies 中,对我来说在那里有一个副本确实有意义,因为这意味着我需要 grunt 包供我自己使用,但是我的图书馆的用户也可以使用他们自己的版本,只要它尊重 peerDependencies 版本锁定。那是对的吗?还是 OP 的例子很糟糕? 我可以想象创建 Grunt 插件的人是 Grunt 的粉丝 :) 因此,他们自己使用 Grunt 来构建插件的过程似乎很自然......但他们为什么要将他们的插件使用的 Grunt 版本范围锁定到他们用来创建它的构建过程?将其添加为开发依赖项允许他们解耦。基本上有两个阶段:构建时间和运行时间。在构建期间需要开发依赖项。运行时需要常规和对等依赖项。当然,由于依赖关系的依赖,一切都会很快变得混乱:) 感谢您的回答!只是为了澄清一下,在您的示例中,如果 JacksModule 依赖于 JillsModule ^1.0.0JillsModuleJacksModuleYourCoolProject 的对等依赖项使用 JacksModuleJillsModule ^2.0.0,我们将收到对等依赖项警告由 NPM 提供,它会建议我们也安装 JillsModule ^1.0.0。但那会发生什么呢? YourCoolProject 现在有两个版本的JillsModule 可通过import jillsModule from "..." 导入?我怎么记得当我使用JacksModule 时我需要传递一个JillsModule v1.0.0 的实例? @tonix 嗯,确实是版本不兼容的问题。 peerDependencies 不能解决这个问题。但它确实有助于明确问题。因为它会清楚地显示版本不匹配,而不是默默地使用两个版本。选择库的应用程序开发人员必须找到解决方案。 @tonix 或者第三个选项:克隆JacksModule repo,将其升级为依赖JillsModule ^2.0.0,并向项目维护者提供PR。首先提交一个错误说明此依赖项已过时并且您想帮助更新它可能会有所帮助。如果你做了一个好的 PR,大多数库维护者会合并它并感谢你。如果维护者没有响应,你可以将你的 fork 发布到以你的名字命名的 NPM 并使用你的 fork。无论如何,有解决方案,但peerDependencies 不能自行解决。【参考方案3】:

peerDependencies 用最简单的例子解释:


  "name": "myPackage",
  "dependencies": 
    "foo": "^4.0.0",
    "react": "^15.0.0"
  




  "name": "foo"
  "peerDependencies": 
    "react": "^16.0.0"
  

在 myPackage 中运行 npm install 会抛出错误,因为它正在尝试安装仅与 React ^16.0.0 兼容的 React 版本 ^15.0.0foo

peerDependencies 没有安装。

【讨论】:

为什么不把 react 16 作为一个 dep 放在 foo 里面呢?这样 15 和 16 都可用并且 foo 可以使用 16 而 mypackage 可以使用 15? React 是一个在运行时引导的框架,为了让 React 15 和 React 16 存在于同一页面上,你需要同时引导这两个框架,这对于最终用户。如果foo 同时适用于 React 15 和 React 16,那么它可以将其 peerDependency 列为>=15 < 17 nitinsh99 我的回答是用最简单的例子来解释 peerDependencies 的目的,而不是如何摆脱 peerDependencies 抛出的错误 @nitinsh99 在包依赖中添加 react 将提供类似 Hooks - Multiple reacts in a package

以上是关于为啥在 npm 中为插件使用对等依赖项?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 NPM 7.17 不安装我的对等依赖项

为啥“npm install”告诉我“没有安装你必须自己安装对等依赖项:”?

在 npm 包中使用对等依赖项

如何识别哪些 npm 包只是对等依赖项?

如何将 npm 依赖项添加为对等依赖项

安装拉力编码依赖项时未安装 NPM 警告所需的对等依赖项