@Daniel_Knights 主要回答了这个问题。但我也想加两分钱。所以这里是:
NPM 中的依赖类型:
为了理解这一点,了解 NPM 包中不同类型的依赖项非常重要。 NPM 中的依赖一般有 4 种类型:
-
直接依赖(或简称为依赖):这些是 NPM 包运行所必需的依赖项。如果您正在使用 express.js 构建 Web 应用程序,那么您绝对希望安装 express 以启动您的应用程序。所以这将是您的应用程序的直接依赖项。这些应列在package.json 的"dependencies": {} 部分下。
-
开发依赖项:这些依赖项在开发应用程序时很有帮助,但不一定由应用程序包运行。这种依赖的一个例子是typescript。 NodeJS 不理解 Typescript。因此,即使您可以在 Typescript 中编写应用程序,但在您通过 typescript 编译器运行它之后,您仍然会使用 Javascript。因此,即使您需要在开发过程中添加 typescript 包,您的应用程序也不需要在编译后运行它。
因此,如果您将typescript 添加到package.json 的"devDependencies": {} 部分并执行npm install,NPM 将安装依赖项和devDependencies。在这个阶段,您可以调用 Typescript 编译器来构建您的应用程序。但在那之后,你可以运行npm prune --production,NPM 会从node_modules/ 中剥离所有 devDependencies。这会减少您的最终应用程序包大小,并使其不受任何开发依赖项的影响。
您不应引用源代码中的任何开发依赖项,除非您的代码安全、优雅地回退,因为该包将在修剪时被删除。
-
可选依赖项:这些是您可以在
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 也会尝试安装一个可选的依赖项。但是如果它无法安装(可能是因为本机绑定不可用),它不会出错。它只会发布警告并继续前进。
现在到问题的依赖类型......
-
对等依赖项:如您所知,这些是您在
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 的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 替换了版本。但是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/parser 和eslint 包一起使用。如果不使用它们,您将无法使用@typescript-eslint/eslint-plugin,因为所有这些都是更大的包eslint 的一部分,它可以帮助您检查Typescript 和JS 代码。所以无论如何你都会安装eslint,这将是使用@typescript-eslint/eslint-plugin的唯一原因。
因此,作者认为添加它们是合适的,因为 @typescript-eslint/eslint-plugin 不在乎,只要您在 5.x.x、6.x.x 或 7.x.x 系列中有任何次要版本的 eslint。 @typescript-eslint/eslint-parser 与 4.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.json。 package.json 的用途是在安装步骤之后完成的。
如果这能澄清您的问题,请告诉我。如果需要,我会添加更多。
新年快乐! :)