Provide a way for a module author to declare alternative versions of dependencies that can be used.
npm does not currently provide a method for a package which supports
node.js >=4
to allow upgrade of a dependency which requires node.js >=6
unless they also bump their own minimum version. This proposal would allow
such authors to declare that an alternative version of the dependency can
be used if that version is already being installed by another dependency.
Generally this would be used when the dependency is only a breaking change
due to dropping support for old node.js versions or when other breaking
changes do not apply to the local module.
Take make-dir
for example. Maybe you maintain a package which still
needs to support node.js 4.x. This restricts you to [email protected]
.
This proposal allows you to declare that [email protected]
or [email protected]
will also work. When users install your module they will get
[email protected]
by default, but if they also install another dependency
which requires [email protected]
your project will use the upgraded version
as well. The idea is that if the updated version is being installed by the
application it is no longer necessary to function on node.js 4.x.
npm would support reading acceptDependencies
object from package.json.
This object would declare alternative versions of packages listed in
dependencies
, devDependencies
, optionalDependencies
or
peerDependencies
.
Example package.json
:
{
"name": "my-node4-package",
"engines": {
"node": ">=4"
},
"dependencies": {
"make-dir": "^1.3.0"
},
"acceptDependencies": {
"make-dir": "2.x - 3.x"
}
}
Creating a node.js 8 application and installing this package alone would
produce the following npm ls
:
[email protected] /usr/src/npm/example-app
└─┬ [email protected]
└─┬ [email protected]
└── [email protected]
Now suppose you are also going to use make-dir
directly, so you run npm i make-dir
, installing the latest, 3.0.0. With current versions of npm
this will result in npm ls
showing:
[email protected] /usr/src/npm/example-app
├─┬ [email protected]
│ └── [email protected]
└─┬ [email protected]
└─┬ [email protected]
└── [email protected]
Future versions with support for acceptDependencies
would show:
[email protected] /usr/src/npm/example-app
├─┬ [email protected]
│ └── [email protected]
└─┬ [email protected]
└── [email protected] deduped
Values in the acceptDependencies
object may be any package specifier that
is allowed in other dependency objects within package.json.
In every case, regardless of the type of dependency, the specifier in the
acceptDependencies
object will be considered a valid resolution if
already present.
For example:
// will fetch from the registry, unless the git version is present
// note that this will allow ANY version, as long as it comes from that
// git repo, but only 1.x otherwise.
{
"dependencies": {
"@user/project": "1.x"
},
"acceptDependencies": {
"@scope/dep": "git+ssh://[email protected]:user/project.git"
}
}
// inverse of the above. fetch from the git repository, UNLESS a 1.x
// version is already present in the tree as a deduplicated option.
{
"dependencies": {
"@user/project": "git+ssh://[email protected]:user/project.git"
},
"acceptDependencies": {
"@scope/dep": "1.x"
}
}
Some maintainers want to provide support for the oldest possible version of node.js. This often causes a large number of duplicate packages to be installed. Older versions of the dependencies may be slower at runtime and cause a large expansion of install size for applications that largely require up to date dependencies. This proposal intends to reduce the trade-off required to support old versions of node.js.
resolutions
field of yarn.- System-wide
.npmresolutions
to allow global upgrade / remapping to specific versions. - Proposed
overrides
field of npm (ref #39)
All of these options can be more powerful than the idea of
acceptDependencies
. The with resolutions
and .npmresolutions
is that
they would only work if users of a module determine that the dependency can
and should be upgraded. This can result in footguns, takes control out of
the maintainers hands. overrides
shifts the authority from users to
module authors but would require introducing condition expansion into
package-lock.json
.
The Edge
class in @npmcli/arborist
will track the acceptDependencies
value for any type of dependency relationship it tracks.
When testing whether an Edge is valid, check both the spec on the edge, as
well as the accept
spec, and resolve as valid if either spec is
satisfied.
When resolving a specifier to a new Node in the tree, only use the actual
specifier for the Edge, and ignore the acceptDependencies
specifier.
Packages listed in acceptDependencies
are only relevant if they are
also listed as another type of dependency. Otherwise, they are ignored.
This does not address the following use cases, which are out of scope of this RFC.
A package may wish to indicate that it needs to use both module
and
module-plugin
. It can work with either the 1.x
or 2.x
versions of
these dependencies, but they must be compatible versions in order to work
properly. That is, it can install either [email protected]
and
[email protected]
, or it can install [email protected]
and
[email protected]
.
This is properly addressed by one of these modules indicating a peer dependency on the other. (Typically the plugin indicating that it has a peer dependency on the core module.)
The tree design algorithm in npm v7 is capable of resolving
peerDependencies
properly, so it will behave correctly. Even if peer
dependencies are omitted from the actual reification, the tree will be
designed such that it would not cause a conflict if they were installed.
It would be nice to be able to indicate that an optional dependency of an installed dependency should not be installed, or even attempted.
For example, a package small-pkg
has an optional dependency on
huge-pkg
. A user may wish to install small-pkg
, but not fetch
huge-pkg
.
optionalDependencies
does not provide a way to indicate that anything
should not be installed.