Verify latest release
pnpm version
11.2.2
Which area(s) of pnpm are affected? (leave empty if unsure)
No response
Link to the code that reproduces this issue or a replay of the bug
https://github.com/astegmaier/playground-pnpm-circular-peers-bug
Reproduction steps
pnpm-workspace.yaml
packages:
- 'packages/*'
dedupePeerDependents: true
packages/a/package.json
{
"name": "a",
"dependencies": {
"esbuild": "^0.27.0",
"webpack": "^5.107.0"
}
}
packages/b/package.json
{
"name": "b",
"dependencies": {
"webpack": "^5.107.0"
}
}
webpack's package.json
{
"name": "webpack",
"dependencies": {
"terser-webpack-plugin": "^5.3.10" // terser-webpack-plugin points back to webpack as a required peer
// ...
}
}
terser-webpack-plugin's package.json
{
"name": "terser-webpack-plugin",
"peerDependencies": {
"webpack": "^5.1.0" // webpack points back at terser-webpack-plugin as a regular dependency
},
"peerDependenciesMeta": {
"esbuild": { "optional": true }
// ...
}
}
Actual pnpm-lock.yaml (two webpack snapshots)
snapshots:
webpack@5.107.0:
dependencies:
terser-webpack-plugin: 5.6.0(webpack@5.107.0)
...
transitivePeerDependencies:
- esbuild
- ...
webpack@5.107.0(esbuild@0.27.7):
dependencies:
terser-webpack-plugin: 5.6.0(esbuild@0.27.7)(webpack@5.107.0)
...
transitivePeerDependencies:
- esbuild
- ...
Describe the Bug
When a package's regular dependency forms a peer cycle with it (the dep declares a required peer back on its parent) and that dependency also declares an optional peer that some consumers activate but others don't, pnpm creates two parent snapshots of the parent and dedupePeerDependents fails to merge them.
In this particular case, the failure to merge them can cause TypeError: The 'compilation' argument must be an instance of Compilation when running webpack in code that spans packages that resolve to the different peer contexts. This symptom is similar to the one reported in #9427 (although no clear reproduction was identified there).
Expected Behavior
Expected pnpm-lock.yaml (only one webpack snapshot)
snapshots:
webpack@5.107.0(esbuild@0.27.7):
dependencies:
terser-webpack-plugin: 5.6.0(esbuild@0.27.7)(webpack@5.107.0)
...
This seems to be the behavior the dedupePeerDependents docs promise for projects that share a dep and differ only on an optional peer: pick the richer snapshot, point both projects at it.
In fact, the docs use webpack + esbuild as their specific paradigm case:
let's say we have a workspace with two projects and both of them have webpack in their dependencies. webpack has esbuild in its optional peer dependencies, and one of the projects has esbuild in its dependencies. In this case, pnpm will link two instances of webpack to the node_modules/.pnpm directory: one with esbuild and another one without it… you may now use the dedupePeerDependents setting to deduplicate webpack when it has no conflicting peer dependencies… both projects will use the same webpack instance, which is the one that has esbuild resolved.
The only thing the docs get wrong is that esbuild is not a peerDependency of webpack -- it's a transitive peer pulled in by terser-webpack-plugin (and possibly other dependencies too).
Which Node.js version are you using?
24.16.0
Which operating systems have you used?
If your OS is a Linux based, which one it is? (Include the version if relevant)
No response
Verify latest release
pnpm version
11.2.2
Which area(s) of pnpm are affected? (leave empty if unsure)
No response
Link to the code that reproduces this issue or a replay of the bug
https://github.com/astegmaier/playground-pnpm-circular-peers-bug
Reproduction steps
pnpm-workspace.yamlpackages/a/package.json{ "name": "a", "dependencies": { "esbuild": "^0.27.0", "webpack": "^5.107.0" } }packages/b/package.json{ "name": "b", "dependencies": { "webpack": "^5.107.0" } }webpack's
package.json{ "name": "webpack", "dependencies": { "terser-webpack-plugin": "^5.3.10" // terser-webpack-plugin points back to webpack as a required peer // ... } }terser-webpack-plugin's
package.json{ "name": "terser-webpack-plugin", "peerDependencies": { "webpack": "^5.1.0" // webpack points back at terser-webpack-plugin as a regular dependency }, "peerDependenciesMeta": { "esbuild": { "optional": true } // ... } }Actual
pnpm-lock.yaml(two webpack snapshots)Describe the Bug
When a package's regular dependency forms a peer cycle with it (the dep declares a required peer back on its parent) and that dependency also declares an optional peer that some consumers activate but others don't, pnpm creates two parent snapshots of the parent and
dedupePeerDependentsfails to merge them.In this particular case, the failure to merge them can cause
TypeError: The 'compilation' argument must be an instance of Compilationwhen running webpack in code that spans packages that resolve to the different peer contexts. This symptom is similar to the one reported in #9427 (although no clear reproduction was identified there).Expected Behavior
Expected
pnpm-lock.yaml(only one webpack snapshot)This seems to be the behavior the
dedupePeerDependentsdocs promise for projects that share a dep and differ only on an optional peer: pick the richer snapshot, point both projects at it.In fact, the docs use
webpack+esbuildas their specific paradigm case:The only thing the docs get wrong is that
esbuildis not apeerDependencyofwebpack-- it's a transitive peer pulled in byterser-webpack-plugin(and possibly other dependencies too).Which Node.js version are you using?
24.16.0
Which operating systems have you used?
If your OS is a Linux based, which one it is? (Include the version if relevant)
No response