npm 我应该在“package.json”的“dependencies”字段中重复“peerDependencies”吗?

ac1kyiln  于 2023-04-12  发布在  其他
关注(0)|答案(2)|浏览(126)

为了实验,我下载了@typescript-eslint/eslint-plugin的源代码。这个包有两个对等依赖:

{
  "peerDependencies": {
    "@typescript-eslint/parser": "^4.0.0",
    "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
  },
  "dependencies": {
    "@typescript-eslint/experimental-utils": "4.11.1",
    "@typescript-eslint/scope-manager": "4.11.1",
    "debug": "^4.1.1",
    "functional-red-black-tree": "^1.0.1",
    "regexpp": "^3.0.0",
    "semver": "^7.3.2",
    "tsutils": "^3.17.1"
  },
}

如果我在安装完所有依赖项后运行npm list,我将得到:

npm ERR! peer dep missing: eslint@^5.0.0 || ^6.0.0 || ^7.0.0, required by @typescript-eslint/eslint-plugin@4.11.1
npm ERR! peer dep missing: eslint@*, required by @typescript-eslint/experimental-utils@4.11.1

这是否意味着npm想要:

{
  "peerDependencies": {
    "@typescript-eslint/parser": "^4.0.0",
    "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
  },
  "dependencies": {
    "@typescript-eslint/parser": "^4.0.0",
    "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
     // ...
  }
}
elcex8rz

elcex8rz1#

@丹尼尔_Knights回答了这个问题。但我也想补充我的两分钱。所以这里是:

NPM中的依赖类型:

为了理解这一点,重要的是要理解NPM包中不同类型的依赖关系。通常,NPM中有4种类型的依赖关系:
1.直接依赖(或简称依赖):这些是NPM包运行所必需的依赖项。如果您正在使用express.js构建Web应用程序,那么您绝对希望安装express包以 Boot 您的应用程序。因此,这将是您的应用程序的直接依赖项。这些应该在package.json"dependencies": {}部分列出。
1.开发依赖:这些依赖关系在开发应用程序时很有帮助,但不一定会被应用程序包使用来运行。这种依赖关系的一个例子是typescript。NodeJS不理解Typescript。因此,即使您可以使用Typescript编写应用程序,在通过Typescript编译器运行后,所以即使你需要在开发过程中添加typescript包,你也不需要它来让你的应用程序在编译后运行。
因此,如果您将typescript添加到package.json中的"devDependencies": {}部分并执行npm install,NPM将安装依赖项和devDependencies。在此阶段,您可以调用Typescript编译器来构建应用程序。但在此之后,您可以运行npm prune --production,NPM将从node_modules/中剥离所有devDependencies。这减少了最终应用程序包的大小,并使其不受任何dev依赖项的影响。

您不应该在源代码中引用任何dev依赖项,而不允许您的代码安全而优雅地回退到替代方案,因为该包将在修剪时被删除。

1.可选依赖:这些是你可以在package.json"optionalDependencies": {}部分指定的依赖项。当你指定一个依赖项为可选时,你让NPM知道 “如果它可用,你的程序将使用这个依赖项。如果它不可用,那也很酷。它将使用其他东西。”
一个常见的情况是使用数据库驱动程序,用JS编写的数据库驱动程序不是特别高效,所以通常使用带有本地绑定的驱动程序(使用原生(C/C++)包来运行其任务的JS库)。但问题是对于原生绑定,native包必须安装在运行应用程序的机器上。这可能并不总是可用的。所以我们可以指定一个native库作为可选库。您可以在JS代码中引用它,如:

var pg = require('pg-native'); // Native binding library
if (!pg) {                     // If it's not available...
  pg = require('pg');          // ...use non native library.
}

因此,当使用npm install安装软件包时,NPM也会尝试安装一个可选的依赖项。但是如果它无法安装(可能是因为本机绑定不可用),它不会出错。它只会发布一个警告并继续。
现在我们来谈谈这种依赖性。
1.对等依赖:正如你已经知道的,这些是你在package.json"peerDependencies": {}部分中指定的依赖。与上面的其他三个依赖不同,NPM在执行npm install时不会尝试安装对等依赖。这是因为NPM期望这些依赖由 other 依赖提供。
我们将看到为什么这是有意义的,但我们必须采取一个非常短的弯路,以了解NPM如何在node_modules/文件夹中构建依赖关系。

NPM如何存储依赖

让我们用一个例子来做这件事:
我们将初始化一个npm包并安装express作为依赖项:

$ npm install express --save

如果我们现在查看node_modules/目录,我们可以看到它已经安装了qs包沿着express

$ ls -l node_modules/
total 196
// ...more stuff...
drwxr-xr-x 3 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 express <---------- here is our express
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 finalhandler
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 forwarded
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 fresh
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 http-errors
drwxr-xr-x 4 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 iconv-lite
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 inherits
drwxr-xr-x 3 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 ipaddr.js
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 media-typer
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 merge-descriptors
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 methods
drwxr-xr-x 3 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 mime
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 mime-db
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 mime-types
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 ms
drwxr-xr-x 3 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 negotiator
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 on-finished
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 parseurl
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 path-to-regexp
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 proxy-addr
drwxr-xr-x 5 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 qs <---------- focus here for a bit
drwxr-xr-x 2 rajshrimohanks rajshrimohanks 4096 Dec 31 16:00 range-parser
// ...even more stuff ...

现在,express/文件夹中没有node_modules/文件夹,尽管它有一个package.json

$ ls -l node_modules/express/
total 132
-rw-r--r-- 1 rajshrimohanks rajshrimohanks 109589 Oct 26  1985 History.md
-rw-r--r-- 1 rajshrimohanks rajshrimohanks   1249 Oct 26  1985 LICENSE
-rw-r--r-- 1 rajshrimohanks rajshrimohanks   4607 Oct 26  1985 Readme.md
-rw-r--r-- 1 rajshrimohanks rajshrimohanks    224 Oct 26  1985 index.js
drwxr-xr-x 4 rajshrimohanks rajshrimohanks   4096 Dec 31 16:00 lib
-rw-r--r-- 1 rajshrimohanks rajshrimohanks   3979 Dec 31 16:00 package.json

如果您查看express包的package.json,您会看到它需要qs包,版本为6.7.0

$ cat node_modules/express/package.json
{
  // other stuff ...

  "dependencies": {
    "accepts": "~1.3.7",
    "array-flatten": "1.1.1",
    "body-parser": "1.19.0",
    "content-disposition": "0.5.3",
    "content-type": "~1.0.4",
    "cookie": "0.4.0",
    "cookie-signature": "1.0.6",
    "debug": "2.6.9",
    "depd": "~1.1.2",
    "encodeurl": "~1.0.2",
    "escape-html": "~1.0.3",
    "etag": "~1.8.1",
    "finalhandler": "~1.1.2",
    "fresh": "0.5.2",
    "merge-descriptors": "1.0.1",
    "methods": "~1.1.2",
    "on-finished": "~2.3.0",
    "parseurl": "~1.3.3",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "~2.0.5",
    "qs": "6.7.0", <-------------- this is what we are looking at
    "range-parser": "~1.2.1",
    "safe-buffer": "5.1.2",
    "send": "0.17.1",
    "serve-static": "1.14.1",
    "setprototypeof": "1.1.1",
    "statuses": "~1.5.0",
    "type-is": "~1.6.18",
    "utils-merge": "1.0.1",
    "vary": "~1.1.2"
  },

  // ... more stuff ...
}

因此express需要qs版本6.7.0,因此NPM将其放在express旁边供其使用。

$ cat node_modules/qs/package.json
{
  // ... stuff ...

  "name": "qs",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ljharb/qs.git"
  },
  "scripts": {
    "coverage": "covert test",
    "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js",
    "lint": "eslint lib/*.js test/*.js",
    "postlint": "editorconfig-tools check * lib/* test/*",
    "prepublish": "safe-publish-latest && npm run dist",
    "pretest": "npm run --silent readme && npm run --silent lint",
    "readme": "evalmd README.md",
    "test": "npm run --silent coverage",
    "tests-only": "node test"
  },
  "version": "6.7.0" <---- this version
}

现在让我们看看如果我们想在我们的应用程序中使用qs,但版本为6.8.0,会发生什么。

$ npm install qs@6.8.0 --save
npm WARN dep-test@1.0.0 No description
npm WARN dep-test@1.0.0 No repository field.

+ qs@6.8.0
added 2 packages from 1 contributor, updated 1 package and audited 52 packages in 0.796s
found 0 vulnerabilities

$ cat node_modules/qs/package.json
{
  //... other stuff ...

  "name": "qs",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ljharb/qs.git"
  },
  "scripts": {
    "coverage": "covert test",
    "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js",
    "lint": "eslint lib/*.js test/*.js",
    "postlint": "eclint check * lib/* test/*",
    "prepublish": "safe-publish-latest && npm run dist",
    "pretest": "npm run --silent readme && npm run --silent lint",
    "readme": "evalmd README.md",
    "test": "npm run --silent coverage",
    "tests-only": "node test"
  },
  "version": "6.8.0" <-------- the version changed!
}

NPM用我们想要的6.8.0替换了这个版本。但是express包需要qs6.7.0上呢?不用担心,NPM通过给express提供qs6.7.0上的本地副本来解决这个问题。

$ cat node_modules/express/node_modules/qs/package.json
{
  // ... other stuff ...

  "name": "qs",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ljharb/qs.git"
  },
  "scripts": {
    "coverage": "covert test",
    "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js",
    "lint": "eslint lib/*.js test/*.js",
    "postlint": "editorconfig-tools check * lib/* test/*",
    "prepublish": "safe-publish-latest && npm run dist",
    "pretest": "npm run --silent readme && npm run --silent lint",
    "readme": "evalmd README.md",
    "test": "npm run --silent coverage",
    "tests-only": "node test"
  },
  "version": "6.7.0" <----- just what express wants!
}

所以你可以看到NPM单独为express添加了一个本地node_modules,并给出了自己的版本。这就是NPM如何确保我们的应用程序以及express都满足自己的要求。但这里有一个关键的要点:

***“如果多个软件包需要另一个通用但版本不同的软件包,NPM将为每个软件包安装多个副本,以满足它们。

在某些情况下,这可能并不总是理想的。假设我们的软件包想要使用qs,但我们不关心它是什么版本,只要它高于6.0.0版本,并且我们确信其他一些软件包(如express)也会一起使用(它在6.7.0上有自己的qs)。在这种情况下,我们可能不希望NPM安装另一个副本增加批量。相反,我们可以指定qs作为...peer dependency
现在,NPM不会自动安装对等依赖,而是期望其他包提供它。
最后说到你的案子...
@typescript-eslint/eslint-plugin的情况下:

{
  "peerDependencies": {
    "@typescript-eslint/parser": "^4.0.0",
    "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
  },
  "dependencies": {
    "@typescript-eslint/experimental-utils": "4.11.1",
    "@typescript-eslint/scope-manager": "4.11.1",
    "debug": "^4.1.1",
    "functional-red-black-tree": "^1.0.1",
    "regexpp": "^3.0.0",
    "semver": "^7.3.2",
    "tsutils": "^3.17.1"
  },
}

@typescript-eslint/eslint-plugin是用于@typescript-eslint/parsereslint包的。如果不使用这些包,你就不可能使用@typescript-eslint/eslint-plugin,因为所有这些都是一个更大的包eslint的一部分,它可以帮助你编写Typescript和JS代码。所以你已经安装了eslint,这将是使用@typescript-eslint/eslint-plugin的唯一原因。
因此,作者认为添加themn是合适的,因为只要您在5.x.x6.x.x7.x.x系列中有eslint的任何次要版本,@typescript-eslint/eslint-plugin就不在乎。
唷!那是一段相当长的旅程,但希望这回答了你的问题!:)

根据评论编辑:

现在假设我fork了@typescript-eslint/eslint-plugin,并希望所有提及的ERR!消息消失。如果我将eslint和parser添加到fork包的依赖项中,peerDependencies变得毫无意义。我应该将它们添加到devDependencies中吗?
你可以这样做,但是这样做会使devDependencies变得毫无意义。你必须明白的是,package.json文件只是一个清单,用来指示NPM在其他人“安装”这个包时该怎么做-无论是作为依赖项安装在另一个包中,还是单独作为全局包安装。
无论如何,package.json就像是NPM的指导手册。它不会以任何方式影响您作为开发人员。因此,如果您想做的只是添加eslint@typescript-eslint/eslint-parser用于开发目的,您可以简单地执行以下操作:

$ npm install --no-save eslint @typescript-eslint/eslint-parser

--no-save标志告诉NPM不要将这些添加到package.json中,而是获取包并将其放在node_modules/目录中。当您运行应用程序时,它所做的就是查看node_modules/中的包,而不是package.jsonpackage.json的目的是在安装步骤之后完成。
如果这澄清了你的问题,请告诉我。如果需要,我会补充更多。
新年快乐!

w8rqjzmb

w8rqjzmb2#

peerDependencies字段旨在与库/插件一起使用,作为一种让安装应用知道需要哪些依赖项才能工作的方式,而不会在dependencies字段中向包本身添加额外的批量。
关于docs
作为一个包管理器,在安装依赖项时,npm的大部分工作是管理它们的版本。但是它通常的模型,在package.json中使用“dependencies”散列,显然福尔斯于插件。大多数插件实际上从不依赖于它们的主机包,即grunt插件从不做require("grunt"),所以即使插件确实将其主机包作为依赖项,下载的副本将永远不会被使用。所以我们将回到起点,您的应用程序可能会将插件插入到与之不兼容的主机包中。
这个想法是,你在devDependencies中安装这个包来开发这个包,然后发布它没有这个依赖,然后任何试图使用你的包并且没有安装这个对等依赖的应用程序都会收到一个错误:

npm ERR! peerinvalid The package flatiron does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer flatiron-cli-config@0.1.3 wants flatiron@~0.1.9
npm ERR! peerinvalid Peer flatiron-cli-users@0.1.4 wants flatiron@~0.3.0

您收到的错误只是说@typescript-eslint/eslint-plugin需要您安装eslint才能正常工作。
因此,显而易见的答案是运行npm i -D eslint将其保存为dev-dependency。但是,此插件是typescript-eslint包的子目录,贡献者忘记将eslint添加为dev-dependency的可能性似乎不太可能,因此,可以安全地假设,它不需要安装用于开发。
在不知道它内部如何工作的情况下,我会说,由于typescript-eslint是使用@typescript-eslint/eslint-plugin所必需的,因此为父包开发任何插件都需要通过父包本身进行。
如果你看一下contribution-guide,它提到了从根目录开发:
在这个repo中开发很容易:

  • 首先fork repo,然后在本地克隆它。
  • 创建新分支。
  • 在项目的根目录中,运行yarn install。
  • 这将安装依赖项,链接包并进行构建。
  • 进行所需的更改。

我不是Maven,所以对此持保留态度,但是,与其直接在子目录中工作,我认为您需要从项目的根目录中处理插件。

相关问题