NodeJS pnpm为完全相同版本的相同依赖项提供不同的散列,从而破坏了nestjs

njthzxwz  于 2023-02-21  发布在  Node.js
关注(0)|答案(3)|浏览(206)

我有一个monorepo与一个非常基本的设置可用于重现这个问题here:
这是一个单独的nestjs应用程序,有2个包可供读取。
软件包和主应用都需要@nestjs/core以及其他依赖项才能正常工作,并且它不仅在本地package.json上强制为完全相同的固定版本,而且在主package.json中也强制为resolutions {}配置。
我可以检查锁定文件,发现虽然使用了相同的版本,但哈希不同,这导致了nestjs的主要问题,无法可靠地导入可注入依赖项,导致它在引导时中断。

是否有办法防止这种情况?强制链接完全相同的哈希/依赖项?

eufgjt7s

eufgjt7s1#

当依赖项具有对等依赖项时,如果对等依赖项在依赖关系图的不同部分中以不同方式解析,则可能会多次将其写入node_modules。
在您的示例中,@nestjs/core位于graphql-server项目和@myapp/entities项目的依赖项中。@nestjs/core具有@nestjs/platform-express作为可选的对等依赖项。
@nestjs/platform-expressgraphql-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/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项目的依赖项中,它应该与其他项目中的版本相同。

ymzxtsji

ymzxtsji2#

pnpm docs开始

- 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-parent-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 == =@nestjs/microservices。尽管这里使用的示例是针对"不同版本"的,但这同样适用于可选的对等依赖项。因此,为了在您的上下文中重新说明该示例:

- 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 discord,这似乎是使用pnpm + nest的开发人员遇到的常见问题。

溶液

使用pnpm hooks在解析时修改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. cjs。我建议使用Nx或其他软件包管理器,Nest/有状态软件包可以更好地工作。

czfnxgou

czfnxgou3#

1.在.npmrc中设置use-lockfile-v6=true
1.然后pnpm install以获取新的锁定文件
1.然后分析pnpm-lock.yaml的顶部,看看你的monorepo的哪些包(你的)出现了不同的版本。
这是一个有点手工的工作,但从上到下开始,并查看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具有@emotion/*依赖项,这些依赖项没有指定到packageB中,使它们不匹配,因为@mui/material将它们列为peerDependencies。因为我不再需要@emotion/*依赖项,我只是删除了它们。然后是pnpm install,现在只有version: 5.10.16(@types/react@18.0.26)(react-dom@18.2.0)(react@18.2.0)。这意味着它将对packageApackageB使用完全相同的软件包。
如果我需要packageA中的@emotion/*,我可以将它们添加到packageB中,这将产生相同的合并依赖关系的结果。这个想法只是为了修复我的不同@mui/materialpeerDependencies版本,以便它们可以匹配并合并。
我认为这也有助于确保你的直接依赖项在monorepo中的任何地方都使用相同的版本(这是对齐peerDependencies之前的第一步。为此,我在我的根package.json中使用:

"pnpm": {
    "overrides": {
      "@mui/material": "^5.10.16",
      "react": "^18.2.0",
      "react-dom": "^18.2.0",
    }
  },

(对每个不希望看到“重复”的依赖项重复此过程...)

  • (注意,这是我自己的经验,也许并不完美,但它至少帮助了我^^......)*

相关问题