【问题标题】:Should I duplicate "peerDependencies" in "dependencies" field of "package.json"?我应该在“package.json”的“dependencies”字段中复制“peerDependencies”吗?
【发布时间】:2021-04-05 19:14:31
【问题描述】:

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

【问题讨论】:

    标签: npm package.json


    【解决方案1】:

    @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。这会减少您的最终应用程序包大小,并使其不受任何开发依赖项的影响。

    您不应引用源代码中的任何开发依赖项,除非您的代码安全、优雅地回退,因为该包将在修剪时被删除。

    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 也会尝试安装一个可选的依赖项。但是如果它无法安装(可能是因为本机绑定不可用),它不会出错。它只会发布警告并继续前进。

    现在到问题的依赖类型......

    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.0qs 包:

    $ 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 包的需求呢?在6.7.0 上需要qs?不用担心,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 将为每个包安装多个副本以满足它们。”

    在某些情况下,这可能并不总是理想的。假设我们的包想要使用qs,但我们不关心它是什么版本,只要它高于版本6.0.0,我们确信其他一些包,比如express也将一起使用(其中在6.7.0 有自己的qs)。在这种情况下,我们可能不希望 NPM 安装另一个副本来增加批量。相反,我们可以将 qs 指定为...对等依赖项

    现在 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.x6.x.x7.x.x 系列中有任何次要版本的 eslint@typescript-eslint/eslint-parser4.x.x 版本类似。


    哇!那是一段旅程,但希望这能回答您的问题! :)


    根据评论编辑:

    现在假设我分叉了@typescript-eslint/eslint-plugin 并想要所有错误!所提及的消息消失。如果我将 eslint 和解析器添加到分叉包的依赖项中,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 的用途是在安装步骤之后完成的。

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

    新年快乐! :)

    【讨论】:

    • 感谢您的详细解答!我接受了你的回答,投票并给了你奖品。请允许我再问一个实际问题。我知道如果我安装了@typescript-eslint/eslint-plugin 作为依赖项,我也需要安装eslint@typescript-eslint/parser。不假设我分叉了@typescript-eslint/eslint-plugin 并希望所有提到的ERR! 消息都消失。如果我将eslint 和解析器添加到分叉包的dependenciespeerDependencies 变得毫无意义。我应该将它们添加到devDependencies 吗?
    • @TakesiTokugawaYD 我已经更新了我的答案,并在下面进行了编辑以回答您的问题。 :) 新年快乐!
    • 你也是!你的礼物,正如我承诺的那样~_^
    • @TakesiTokugawaYD 哈哈...谢谢:)
    【解决方案2】:

    peerDependencies 字段旨在与库/插件一起使用,作为让安装应用程序知道其工作所需的依赖项的一种方式,而无需在 dependencies 字段中向包本身添加额外的体积。

    来自docs

    作为包管理器,安装依赖项时 npm 的大部分工作是管理它们的版本。但是它通常的模型,在 package.json 中有一个“依赖”哈希,显然不适合插件。大多数插件实际上从不依赖于它们的主机包,即 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 以将其保存为开发依赖项。 但是,这个插件是 typescript-eslint 包的子目录,贡献者忘记添加 eslint 作为开发依赖项的可能性似乎不大,因此,可以安全地假设,开发时不需要安装它。

    在不知道其内部工作原理的情况下,我想说,由于使用 @typescript-eslint/eslint-plugin 需要 typescript-eslint,因此为父包开发任何插件都需要通过父包本身进行。

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

    在这个 repo 中开发很容易:

    • 首先 fork 存储库,然后将其克隆到本地。
    • 创建一个新分支。
    • 在项目的根目录下,运行 yarn install。
    • 这将安装依赖项、链接包并进行构建。
    • 进行必要的更改。

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

    【讨论】:

    • 感谢您的回答!很抱歉,我只能将声誉奖授予一位回答者...
    猜你喜欢
    • 2019-05-30
    • 2017-12-26
    • 2020-04-15
    • 1970-01-01
    • 1970-01-01
    • 2021-09-07
    • 2015-06-18
    • 2012-12-31
    • 1970-01-01
    相关资源
    最近更新 更多