我有一个单回购,它有一个非常基本的设置,可以在这里重现这个问题:
它是一个单独的nestjs应用程序,有两个包可供读取。
@nestjs/core
和其他依赖项是包和主应用程序工作所必需的,并且它被强制为完全相同的固定版本,不仅在它们自己的本地包.json上,而且在主包.json中的resolutions {}
配置中也是如此。
我可以检查锁定文件,发现尽管使用了相同的版本,但哈希不同,导致nestjs出现重大问题,无法可靠地导入可注入的依赖项,导致它在引导时中断。
有办法防止这种情况发生吗?强制链接完全相同的散列/依赖项
编辑:
使用pnpm v7.29.0,您不再需要执行下面描述的破解操作,只需将其留在此处用于教育目的。
现在的解决方案只是设置dedupe-peer-dependents=true
(例如在您的.npmrc
中)。
来自pnpm文档
- foo-parent-1
- bar@1.0.0
- baz@1.0.0
- foo@1.0.0
- foo-parent-2
- bar@1.0.0
- baz@1.1.0
- foo@1.0.0
在上面的例子中,foo@1.0.0是为foo-parent-1和foo-parend-2安装的。这两个包都有bar和baz,但它们依赖于不同版本的baz。因此foo@1.0.0具有两组不同的依赖项:一组具有baz@1.0.0另一个baz@1.1.0.为了支持这些用例,pnpm必须硬链接foo@1.0.0存在不同依赖集的次数。
对于您的特定情况,foo===@nestjs/core,baz===@nessjs/micservices。尽管这里使用的例子是";不同版本";,这同样适用于可选的对等依赖关系。因此,为了重新说明这个例子,在您的上下文中:
- my-nestjs-app
- @nestjs/microservices@9.1.4
- @nestjs/core@9.1.4
- my-other-nestjs-app
- @nestjs/core@9.1.4
通常,如果一个包没有对等依赖项,它会硬链接到其依赖项符号链接旁边的node_modules文件夹,如下所示:
然而,如果foo[@nestjs/core]具有对等依赖关系,则可能有多组依赖关系,因此我们为不同的对等依赖关系解析创建不同的集合
^这通常适用于大多数软件包。然而@nestjs/core是特别的。它是有状态的,因此它可以处理所有的运行时依赖项注入。pnpm在monorepo中创建@nestjs/core的多个副本会导致您看到的令人困惑的行为,因为您的应用程序可能依赖于一个副本,而其他nestjs库则依赖于另一个副本。根据NestJS的不和,这似乎是使用pnpm+nest的开发人员普遍遇到的问题。
解决方案
使用pnpm钩子在解析时间修改nestjs包的peerDependenciesMeta
:
// .pnpmfile.cjs in your monorepo's root
function readPackage(pkg, context) {
if (pkg.name && pkg.name.startsWith('@nestjs/')) {
context.log(`${pkg.name}: make all peer dependencies required`);
pkg.peerDependenciesMeta = {};
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
}
};
这是一个破解IMO,处理起来真的很烦人,因为Renovate
/Dependabot
在执行依赖关系更新时会忽略.pnpmfile.js。我建议使用Nx或Nest/有状态包更适合使用的其他包管理器。
当依赖关系具有对等依赖关系时,如果在依赖关系图的各个部分中对对等依赖关系进行不同的解析,则可能会多次将其写入node_modules。
在您的案例中,@nestjs/core
位于graphql-server
项目和@myapp/entities
项目的依赖项中。CCD_ 11具有CCD_ 12作为可选的对等依赖。
@nestjs/platform-express
在graphql-server
项目的依赖项中,因此pnpm将其链接到@nestjs/platform-express
。你可以在锁定文件中看到它:
/@nestjs/core/8.4.7_fkqgj3xrohk2pflugljc4sz7ea:
resolution: {integrity: sha512-XB9uexHqzr2xkPo6QSiQWJJttyYYLmvQ5My64cFvWFi7Wk2NIus0/xUNInwX3kmFWB6pF1ab5Y2ZBvWdPwGBhw==}
requiresBuild: true
peerDependencies:
'@nestjs/common': ^8.0.0
'@nestjs/microservices': ^8.0.0
'@nestjs/platform-express': ^8.0.0
'@nestjs/websockets': ^8.0.0
reflect-metadata: ^0.1.12
rxjs: ^7.1.0
peerDependenciesMeta:
'@nestjs/microservices':
optional: true
'@nestjs/platform-express':
optional: true
'@nestjs/websockets':
optional: true
dependencies:
'@nestjs/common': 8.4.7_47vcjb2de6lyibr6g4enoa5lyu
'@nestjs/platform-express': 8.4.7_7tsmhnugyerf5okgqzer2mfqme # <------HERE
'@nuxtjs/opencollective': 0.3.2
fast-safe-stringify: 2.1.1
iterare: 1.2.1
object-hash: 3.0.0
path-to-regexp: 3.2.0
reflect-metadata: 0.1.13
rxjs: 7.5.5
tslib: 2.4.0
uuid: 8.3.2
transitivePeerDependencies:
- encoding
在另一个项目(@myapp/entities
)中,@nestjs/platform-express
不在依赖项中,因此在安装@nestjs/core
时,pnpm无法解决可选的对等依赖项。因此,pnpm需要创建@nestjs/core
的另一个实例,该实例没有链接此可选对等体。正如您在锁定文件中看到的,其他条目没有@nestjs/platform-express
:
/@nestjs/core/8.4.7_g7av3gvncewo44y4rurz3mgav4:
resolution: {integrity: sha512-XB9uexHqzr2xkPo6QSiQWJJttyYYLmvQ5My64cFvWFi7Wk2NIus0/xUNInwX3kmFWB6pF1ab5Y2ZBvWdPwGBhw==}
requiresBuild: true
peerDependencies:
'@nestjs/common': ^8.0.0
'@nestjs/microservices': ^8.0.0
'@nestjs/platform-express': ^8.0.0
'@nestjs/websockets': ^8.0.0
reflect-metadata: ^0.1.12
rxjs: ^7.1.0
peerDependenciesMeta:
'@nestjs/microservices':
optional: true
'@nestjs/platform-express':
optional: true
'@nestjs/websockets':
optional: true
dependencies:
'@nestjs/common': 8.4.7_47vcjb2de6lyibr6g4enoa5lyu
'@nuxtjs/opencollective': 0.3.2
fast-safe-stringify: 2.1.1
iterare: 1.2.1
object-hash: 3.0.0
path-to-regexp: 3.2.0
reflect-metadata: 0.1.13
rxjs: 7.5.5
tslib: 2.4.0
uuid: 8.3.2
transitivePeerDependencies:
- encoding
要解决此问题,可以将@nestjs/platform-express
添加到@myapp/entities
项目的依赖项中。它应该与其他项目中的版本相同。
-
在
.npmrc
中设置use-lockfile-v6=true
-
然后
pnpm install
获取新的锁定文件 -
然后分析
pnpm-lock.yaml
的顶部,看看你的单回购的哪些包(你的)出现了不同的版本。
这是一项有点手动的工作,但从上到下开始,查看importers:
块。这是您在package.json
文件中列出的所有直接依赖项。
停在每个看起来有后缀的依赖项上,比如version: 0.31.1(react@18.2.0)
(注意,没有重复风险的是version: 0.31.1
)。
例如,在我的案例中,我为我的packageA
:找到了
'@mui/material':
specifier: ^5.10.16
version: 5.10.16(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0)
然后将'@mui/material':
复制到剪贴板中,并在importer:
部分中搜索所有出现的内容。如果您发现了不同的版本模式,这意味着它已经消除了重复,例如我的packageB
:
'@mui/material':
specifier: ^5.10.16
version: 5.10.16(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0)
在我的案例中,我的packageA
具有未在packageB
中指定的@emotion/*
依赖项,这使得它们不匹配,因为@mui/material
将它们列为peerDependencies
。由于我不再需要@emotion/*
依赖项,我只是删除了它们。然后是CCD_ 41,现在只有CCD_。这意味着它将对packageA
和packageB
使用完全相同的包。
如果我在packageA
中需要@emotion/*
,我可以将它们添加到packageB
中,那么合并依赖关系也会得到同样的结果。这个想法只是修复我的不同@mui/material
的peerDependencies
版本,以便它们可以匹配和合并。在中进行了解释https://pnpm.io/how-peers-are-resolved。
我认为这也有助于确保您的直接依赖项在monoreto中的任何地方都使用相同的版本(这是对齐peerDependencies
之前的第一步。为此,我在根package.json
:中使用
"pnpm": {
"overrides": {
"@mui/material": "^5.10.16",
"react": "^18.2.0",
"react-dom": "^18.2.0",
}
},
(对每个你不想看到"重复"的依赖项重复这个过程…)
(注意,这是根据我自己的经验,它可能并不完美,但它至少帮助了我^^…)