Allow peer dependencies with stricter version ranges
See original GitHub issueContinuation of https://github.com/microsoft/types-publisher/issues/431.
Current state
When you import from a package hosted in the DefinitelyTyped repository in a package that’s also hosted in DefinitelyTyped, the types publisher will automatically declare a direct dependency
on that types package matching any version. For example, if you have a package react-library
that imports from react
then the package.json
of @types/react-library
will look something like this:
{
"name": "@types/react-library",
"dependencies": {
"@types/react": "*"
}
}
Problems
Combination of the described problems are encountered in
- https://github.com/facebook/react/issues/24304
- https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/59839
- https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/59820
- https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/59841
duplication of @types/*
packages
Given an existing module graph similar to
types-peer-deps@1.0.0 /home/eps1lon/Development/throwaway/types-peer-deps
├─┬ @types/react-library@1.0.0
│ └── @types/react@17.0.44 deduped
└── @types/react@17.0.44
If you want to upgrade to React 18 you would need to update @types/react
with npm install @types/react@18
.
However, npm
(and yarn
and probably other package managers) make sure that this install does not alter the dependency tree too much (if you’re using lockfiles). What you end up with is something like
types-peer-deps@1.0.0 /home/eps1lon/Development/throwaway/types-peer-deps
├─┬ @types/react-library@1.0.0
│ └── @types/react@17.0.44
└── @types/react@18.0.0
Now your dependency tree contains multiple version of a types package. In this specific case these packages are incompatible. To fix, one would have to apply one of the workarounds described in https://github.com/facebook/react/issues/24304#issuecomment-1094565891 which isn’t trivial at all. After all, package managers are supposed to resolve these issues. But they can’t if we provide them with inaccurate information.
*
is not forwards compatible
While currently published packages might work perfectly fine with all published versions of @types/react
at the time, there’s no guarantee that continues to work. It essentially requires that no breaking changes are made at all which defeats the purpose of SemVer major releases.
Proposal
Allow peer dependencies
React is generally a peer dependency and therefore it types should as well.
If we could move @types/react
from dependencies
to peer dependencies with
"peerDependencies": {
"@types/react": "*"
},
we would ensure types deduplication since the package owner would defines the version of @types/react
a single time (via dependencies
or devDependency
.
Allow constraining @types/*
dependencies to something stricter than *
Either let maintainers decide the range manually or infer the range from the versions it’s currently tested on. For example, @types/react-library
runs tests with @types/react
v15, v16, v17 and latest. In that case we check what version the current release has (or will be) and declare that.
This way an update to @types/react-library
will also bump versions of dependencies
and therefore propagate bug fixes and features of those dependencies upon release.
related issues
Issue Analytics
- State:
- Created a year ago
- Reactions:17
- Comments:22 (21 by maintainers)
Top GitHub Comments
DefinitelyTyped is a clever service for the TypeScript Ecosystem. The real “correct” fix is for types to be shipped by the official runtime-packages instead of being provided by DefinitelyTyped. Then they would be versioned exactly like the packages they belong to.
In practice, many DefinitelyTyped definitions are maintained by external communities, and aren’t necessarily shipped simultaneously with their source-packages, there are always going to be warts and weak points. Starting with
*
-versions was a good compromise initially, because many types packages really don’t care what version, or rarely have breaking changes. Unfortunately, that heuristic has failed today with a popular package that had breaking changes, and so*
-package deps didn’t work.There’s no point throwing the baby out with the bathwater. Let’s refocus this ticket on the discussion of if “peerDependencies with stricter version ranges” is a valid enhancement to DefinitelyTyped capabilities that is worth the cost, and if there are obvious traps that we will fall into.
An additional point to discuss is the impact if “all packages” follow this recommendation. Do we lead to excess thrash on DT’s repo? (Because people have to make new versions to increase the upper bound every time the source package updates?) Other problems?
Some thinking here is clearly off - if your types depend on
react
’s types, the dependency is in no way optional, it’s mandatory. An “optional” peer dep would only serve to break consumers of the package’s types, since they’d suddenly be missing required type information. I can understand wanting to elevate from a dependency to a peer dependency, since people are used toreact
itself being a peer dep, but all that actually does for types is elevate a (potential! not guaranteed) compile time type conflict to an install time peer dep version conflict - the underlying conflict still exists.Moreover, all of DT is tested to be internally consistent, so the latest versions of all packages on DT should always work together - enumerating how far back in time, and for what version ranges, that holds is practically impossible over a shifting set of dependencies, hence the
*
version ranges to allow any version to be installed (thus, deferring to package manager power users in case of esoteric mix-old-with-new dependency relationships). On top of that, using*
versions allows us to avoid republishing all dependents of a package when it updates just to update the version strings - which we would always need to do, since, again, DT is kept internally consistent at the latest version of every package.What’s odd, to me, is that while a package manage is willing to update the outermost package in its tree, why it would be unwilling to update the inner one even though the version specifier allows it? Especially if it already deduped the versions to a single copy? (The package manager seems like it would have to do more work to move the old install to a new location!) Minimally, I know if you blow away your lockfile and install again, they’ll unify to the single valid top-level install again, which is decidedly not what would be the case of version ranges were stricter - you’d just be guaranteed to have the version conflict.