我应该在 "package.json" 的 "dependencies" 字段中复制 "peerDependencies" 吗?

Should I duplicate "peerDependencies" in "dependencies" field of "package.json"?

为了实验,我下载了@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"
     // ...
  }
}

peerDependencies 字段旨在与 libraries/plugins 一起使用,作为一种让正在安装的应用程序知道其工作需要哪些依赖项的方式,而无需在 dependencies 字段.

来自docs

As a package manager, a large part of npm's job when installing your dependencies is managing their versions. But its usual model, with a "dependencies" hash in package.json, clearly falls down for plugins. Most plugins never actually depend on their host package, i.e. grunt plugins never do require("grunt"), so even if plugins did put down their host package as a dependency, the downloaded copy would never be used. So we'd be back to square one, with your application possibly plugging in the plugin to a host package that it's incompatible with.

想法是您在 devDependencies 中安装包以开发包并发布它 没有 该依赖项,然后任何尝试使用您的包的应用程序和没有安装此 peer-dependency 将收到错误:

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 将其保存为开发依赖项。 但是,这个插件是typescript-eslint包的子目录,贡献者忘记添加eslint作为开发依赖的可能性似乎不大,所以,可以肯定地说,开发不需要安装它。

不知道它在内部是如何工作的,我会说,因为使用 @typescript-eslint/eslint-plugin 需要 typescript-eslint,为父包开发任何插件都需要通过父包本身来完成。

如果您查看 contribution-guide,它提到从根目录开发:

Developing in this repo is easy:

  • First fork the repo, and then clone it locally.
  • Create a new branch.
  • In the root of the project, run yarn install.
  • This will install the dependencies, link the packages and do a build.
  • Make the required changes.

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

@Daniel_Knights 基本中肯地回答了问题。但我也想加上我的两分钱。所以这里是:

NPM 中的依赖类型:

为了理解这一点,了解 NPM 包中不同类型的依赖关系很重要。一般来说,npm中有4种依赖:

  1. 直接依赖(或简称依赖):这些依赖对于NPM包来说是绝对必要的功能。如果您正在使用 express.js 构建 Web 应用程序,那么您绝对希望安装 express 以便您的应用程序启动。所以这将是您的应用程序的直接依赖项。这些应列在 package.json.

    "dependencies": {} 部分下
  2. 开发依赖项:这些依赖项在开发您的应用程序时很有帮助,但应用程序包不一定使用到运行。这种依赖的一个例子是 typescript。 NodeJS 不理解 Typescript。因此,即使您可以使用 Typescript 编写应用程序,但在通过 Typescript 编译器 运行 之后,您将得到 Javascript。因此,即使您需要在开发过程中添加 typescript 包,您的应用程序在编译后也不需要它 运行。

因此,如果您将 typescript 添加到 package.json 中的 "devDependencies": {} 部分并执行 npm install,NPM 将同时安装依赖项和 devDependencies。在此阶段,您可以调用您的 Typescript 编译器来构建您的应用程序。但在那之后,你可以 运行 npm prune --production,NPM 将从 node_modules/ 中剥离所有 devDependencies。这会减少您的最终应用程序包大小,并使其不受任何开发依赖。

你不应该在你的源代码中引用任何开发依赖,而不允许你的代码安全和优雅地回退到替代方案,因为包将在 p运行ing.

  1. 可选依赖项:这些是您可以在 package.json"optionalDependencies": {} 部分中指定的依赖项。当您将依赖项指定为可选时,您让 NPM 知道 “如果此依赖项可用,您的程序将使用它。如果不可用,那也很酷。它将使用其他东西。”

这有助于使用数据库驱动程序的常见场景。用 JS 编写的数据库驱动程序不是特别高效或性能。因此,通常使用具有本机绑定的驱动程序(使用本机 (C/C++) 包来 运行 其任务的 JS 库)。但问题是,对于原生绑定,原生包必须安装在应用所在的机器上运行。这可能并不总是可用。所以我们可以指定一个本地库作为可选的。您可以在 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 也会尝试安装一个可选的依赖项。但是如果它无法安装(可能是因为本机绑定不可用),它不会出错。它只是 post 一个警告然后继续。

现在讨论所讨论的依赖类型...

  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,你会发现它需要版本为 6.7.0:[=98= 的 qs 包]

$ 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。但是 6.7.0 需要 qsexpress 包的需求呢?别担心,NPM 通过在 6.7.0.

处提供 express 它自己的 qs 本地副本来处理它
$ 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 将为每个包安装多个副本以满足它们。”。 =189=]

在某些情况下,这可能并不总是理想的。假设我们的包想要使用 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.

的唯一原因

因此,作者认为添加它们是合适的,因为 @typescript-eslint/eslint-plugin 并不关心,只要您在 5.x.x、[=82] 中有 eslint 的任何次要版本=] 或 7.x.x 系列。对于 @typescript-eslint/eslint-parser 版本 4.x.x.

同样

哇!这是一个很好的旅程,但希望这能回答你的问题! :)


根据评论编辑:

Now assume that I forked the @typescript-eslint/eslint-plugin and want all ERR! messages mentioned in question disappear. If I add eslint and parser to dependencies of forked package, peerDependencies becomes meaningless. Should I add them to devDependencies instead?

可以,但那样会使 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 的目的是在安装步骤之后完成。

如果这能澄清您的问题,请告诉我。如果需要我会添加更多。

新年快乐! :)