rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Define which dependencies are shared among workspace projects

Open isaacs opened this issue 4 years ago • 5 comments

References

isaacs avatar May 05 '21 16:05 isaacs

This is ready for review and discussion. Removed the attractive nuisance of figuring out how to mark things as shared/isolated. If you want it isolated, make it a (non-peer) dep; if you want it hoisted, declare it at the top level.

isaacs avatar Oct 29 '21 21:10 isaacs

cc: @VincentBailly

isaacs avatar Oct 29 '21 21:10 isaacs

It seems that yarn PnP encourages/allows a kind of "escape hatch" around hoisting/dep availability: listing something as optional in peerDependenciesMeta and not listing it at all in peerDependencies. I'm not specifically advocating for this feature, but I wanted to make sure that we leave design space for it if it becomes relevant wrt this RFC, shared mode, or isolated mode.

The use case where this came up: eslint-plugin-import depends on es-module-utils and also eslint-import-resolver-node. es-module-utils implicitly depends on eslint-import-resolver-node. This isn't a problem for consumers of eslint-plugin-import, but consumers of es-module-utils are then forced to directly depend on eslint-import-resolver-node. A user put up a PR to add eslint-import-resolver-node as only an optional peer dep (but not in peerDependencies at all) to eslint-module-utils, which apparently tells yarn pnp (and maybe pnpm) to "allow" access to the dep. Presumably isolated mode would want to do the same? If so, would this RFC want to include optional-only-peer-deps in the "shared" category?

ljharb avatar Nov 03 '21 18:11 ljharb

Summary of discussion from open RFC call 2021-11-03

We're unlikely to add an escape hatch using peerDependenciesMeta, since we already have the escape hatch of "just add a top level dep" if something really needs to be shared with workspaces that don't have a dependency relationship on it.

@ljharb points out that this is a step towards a more sensible default behavior, but would still like to see special handling of peerDependencies in a way that is similar to isolated-mode. That can be an separate extension to this RFC or an alternative that includes all the same behaviors described here. In a nutshell that would mean:

  • peerDependencies are shared, but only among the workspaces that have overlapping/resolvable peer sets.
  • peerDependencies thus shared are not accessible to the root or any other workspace. (Ie, they are placed in a hidden location, eg ./node_modules/.npm-workspace-peers/... and symlinked into place.)

So, for example, if several workspaces have peerDependencies:{react:'16'}, and several others have peerDependencies:{react:'17'}, then they'd end up "isolated" into two groups.

@isaacs brought up implementation challenge that this will pose in cases where peer sets are partially overlapping, how to optimally resolve cases where an overlapping version could be found but it's not an exact match. (For example, one workspace wants [email protected], another wants react@^16.8.0, another wants react@16 <16.10.0. Should that be one group that all resolve to [email protected], or two workspaces with 16.latest and the third with 16.9?)

Consensus seemed to be reached that it's worth doing this proposal first, since it poses no great implementation challenges, and provides some benefits. Even though it doesn't go as far as we might like, it's not contradictory to those other goals, so it could be explored later.

Concerns about excessive duplication could be met by suggesting --mode=isolated, since that maximally dedupes the entire tree while preserving the same resolution semantics as --mode=hoisted.

Brought up the escape hatch to shared peerDeps by listing the peer dep in devDependencies, but of course, that entirely prevents sharing of peerDependencies in cases where the goal is to address ERESOLVE errors in development before they are encountered in production.

Could also tie the shared peerDep behavior to workspace groups (ie, every declared workspace group shares a single peerDependencies set, which will conflict if not resolvable). This would make it explicit, at least, rather than relying on inferring the peer set boundaries from the stated peerDependencies in each workspace.

It seems like this proposal, and the more advanced peerDependencies handling, would somewhat obviate the need for nohoist, since nothing would ever be hoisted above the workspace boundary.

No easy solution was suggested for the concern about cases where the user wants a sibling workspace module to be loaded from the registry rather than by linking to the sibling. (But again, we could use workspace groups as an explicit group boundary here as well.)

isaacs avatar Nov 05 '21 19:11 isaacs

I have a requirement where I have all of my workspaces including a module. but in one of them, I don't want the dependency to be hoisted out of the local. It can still go into the root, but not for one workspace.

Is the solution to use peerDependencies in the workspace for the module I want to be kept local? It is unclear from the discussion here.

metawrap-dev avatar May 03 '22 13:05 metawrap-dev