[GH-ISSUE #502] How peerDependencies affect package versions #4615

Closed
opened 2026-06-13 12:53:24 -05:00 by GiteaMirror · 19 comments
Owner

Originally created by @theetrain on GitHub (Feb 22, 2019).
Original GitHub issue: https://github.com/semver/semver/issues/502

Hello 👋

I'm a big fan of semantic versioning, and use it daily as I maintain the TELUS Design System. If you're curious about how we use semver with visual components, see the TDS FAQ. Forgive me as I'm going to be using npm packages in my examples as I'm unsure how to frame this in generic terms.

As a maintainer of individually-versioned React components, I would like to have guidance on how to version packages as their peerDependencies change.

Situation 1: upgrading peerDependencies

In a situation where a peerDependency's version is bumped to its next major version:

package.json

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
-   "react": ">=15",
+   "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

The host application could potentially have a version of react that is lower than 16.7.0. Would that mean this package should release as a breaking change? (1.0.5 -> 2.0.0)

In my opinion, the package would need to have a breaking change since the host application may be forced to upgrade a pre-existing package to a breaking change, potentially requiring a refactor.

Situation 2: adding peerDependencies

In a situation where a peerDependency is added to a package:

package.json

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
+   "styled-components": "^4.1.3",
    "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

The host application could potentially have a version of styled-components that is lower than 4.1.3, or the host application may not have styled-components installed already. Would that mean this package should release as a breaking change? (1.0.5 -> 2.0.0)

In my opinion, the package would need to have a breaking change since the host application may be forced to upgrade a pre-existing dependency to a breaking version. Or, when adding the package styled-components in particular, it may not work out of the box without necessary configurations to webpack and babel.

Situation 3: removing peerDependencies

In a situation where a peerDependency is removed from a package:

package.json

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
-   "styled-components": "^4.1.3",
    "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

The host application would not need to be refactored in the absence of an unused package. Would that mean there is no need for deprecation notices and the package should release a patch? (1.0.5 -> 1.0.6)

Originally created by @theetrain on GitHub (Feb 22, 2019). Original GitHub issue: https://github.com/semver/semver/issues/502 Hello 👋 I'm a big fan of semantic versioning, and use it daily as I maintain the [TELUS Design System](https://github.com/telus/tds-core). If you're curious about how we use semver with visual components, see the [TDS FAQ](https://github.com/telus/tds-core/blob/bbfd8ef232bfe1a8c7df25660509168b87f2b49c/guide/faq.md#how-is-tds-versioned). Forgive me as I'm going to be using npm packages in my examples as I'm unsure how to frame this in generic terms. **As a maintainer of individually-versioned React components, I would like to have guidance on how to version packages as their `peerDependencies` change.** ## Situation 1: upgrading peerDependencies In a situation where a peerDependency's version is bumped to its next major version: **package.json** ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { - "react": ">=15", + "react": "^16.7.0", "react-dom": ">=15" } } ``` The host application could potentially have a version of `react` that is lower than `16.7.0`. Would that mean this package should release as a breaking change? (`1.0.5` -> `2.0.0`) In my opinion, the package would need to have a breaking change since the host application **may** be forced to upgrade a pre-existing package to a breaking change, potentially requiring a refactor. ## Situation 2: adding peerDependencies In a situation where a peerDependency is added to a package: **package.json** ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { + "styled-components": "^4.1.3", "react": "^16.7.0", "react-dom": ">=15" } } ``` The host application could potentially have a version of `styled-components` that is lower than 4.1.3, or the host application may not have `styled-components` installed already. Would that mean this package should release as a breaking change? (`1.0.5` -> `2.0.0`) In my opinion, the package would need to have a breaking change since the host application **may** be forced to upgrade a pre-existing dependency to a breaking version. Or, when adding the package `styled-components` in particular, it may not work out of the box without necessary configurations to `webpack` and `babel`. ## Situation 3: removing peerDependencies In a situation where a peerDependency is removed from a package: **package.json** ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { - "styled-components": "^4.1.3", "react": "^16.7.0", "react-dom": ">=15" } } ``` The host application would not need to be refactored in the absence of an unused package. Would that mean there is no need for deprecation notices and the package should release a patch? (`1.0.5` -> `1.0.6`)
Author
Owner

@FichteFoll commented on GitHub (Feb 22, 2019):

I don't know how peerDependencies are handled in npm, however in this case I would argue that in a world where all dependencies are properly specified, the package manager would find a conflict when the requirement of a newly added peerDependency does not overlap with the requirement of another library/dependency and thus reject the update.

In that sense, the update is so breaking that it cannot even be installed. The problem is that this is a very inconvenient situation and not covered at all by semver since it is only concerned about the API that a dependency itself defines/exposes, not in terms of what other environment contracts need to be fulfilled.

I suppose in a packaging sense, this situation is comparable to a situation where you drop support for an OS (entirely or just a specific version) where you would need to increase major because of breakage with users on that OS. Unless you never supported that OS in the first place, in which case the user relied on an implementation detail. Considering you need to specify a peerDependency explicitly, I don't think the undocumented case applies here.

Thus, as unfortunate as it sounds, I believe this to be a breaking change (by the tooling) that potentially (albeit unlikely) requires human intervention before it an be adopted.

Note that this only applies if you need a new major version of a dependency because all other sibling dependencies should accept version updates in the same major release family.

<!-- gh-comment-id:466450865 --> @FichteFoll commented on GitHub (Feb 22, 2019): I don't know how peerDependencies are handled in npm, however in this case I would argue that in a world where all dependencies are properly specified, the package manager would find a conflict when the requirement of a newly added peerDependency does not overlap with the requirement of another library/dependency and thus reject the update. In that sense, the update is so breaking that it cannot even be installed. The problem is that this is a very inconvenient situation and not covered at all by semver since it is only concerned about the API that a dependency itself defines/exposes, not in terms of what other environment contracts need to be fulfilled. I suppose in a packaging sense, this situation is comparable to a situation where you drop support for an OS (entirely or just a specific version) where you would need to increase major because of breakage with users on that OS. Unless you never supported that OS in the first place, in which case the user relied on an implementation detail. Considering you need to specify a peerDependency explicitly, I don't think the undocumented case applies here. Thus, as unfortunate as it sounds, I believe this to be a **breaking** change (by the tooling) that potentially (albeit unlikely) requires human intervention before it an be adopted. Note that this only applies if you need a new major version of a dependency because all other sibling dependencies should accept version updates in the same major release family.
Author
Owner
<!-- gh-comment-id:466453001 --> @FichteFoll commented on GitHub (Feb 22, 2019): Related: https://github.com/semver/semver/issues/444, https://github.com/semver/semver/issues/337, https://github.com/semver/semver/issues/341
Author
Owner

@ljharb commented on GitHub (Feb 22, 2019):

In an npm module, any peer dep change that removes a previously valid version from being valid is a major/breaking change. Peer deps are part of the public API.

<!-- gh-comment-id:466501843 --> @ljharb commented on GitHub (Feb 22, 2019): In an npm module, any peer dep change that removes a previously valid version from being valid is a major/breaking change. Peer deps are part of the public API.
Author
Owner

@segiddins commented on GitHub (Feb 23, 2019):

I think this question basically boils down to: "are your set of dependencies part of your API?"

There are a couple of situations that raise this question:

  • Does adding a net-new dependency with any requirement on version count as a breaking change?
  • Does removing a dependency?
  • Changing a requirement?
<!-- gh-comment-id:466636631 --> @segiddins commented on GitHub (Feb 23, 2019): I think this question basically boils down to: "are your set of dependencies part of your API?" There are a couple of situations that raise this question: - Does adding a net-new dependency with _any_ requirement on version count as a breaking change? - Does removing a dependency? - Changing a requirement?
Author
Owner

@ljharb commented on GitHub (Feb 23, 2019):

Dependencies are transparent, and aren’t. Peer dependencies hoist, and are.

<!-- gh-comment-id:466674967 --> @ljharb commented on GitHub (Feb 23, 2019): Dependencies are transparent, and aren’t. Peer dependencies hoist, and are.
Author
Owner

@jwdonahue commented on GitHub (Mar 13, 2019):

When it comes to packages, the manifest is an integral part of your "API". Introduce a breaking change without bumping the major version, you'll garner the wrath of your customers and possibly loose credibility. Do that too many times, and folks will start treating your minor/patch changes as-if they are breaking changes. That could be disaster for you in the case where you must get a security fix out immediately.

Just consider what it is you are putting that version number on and how it affects your customers. Except in the case of a single, stand-alone, dependency-free API; any API(s) that you ship in a package should be versioned separately from the package that contains them. If I pull your latest package and it's only had minor/patch level revision bumps, it better not break my build or product, or I will not be happy. Do that twice, I will complain loudly. By the third time, I will be dropping your product and complaining on every relevant forum I can find.

<!-- gh-comment-id:472294800 --> @jwdonahue commented on GitHub (Mar 13, 2019): When it comes to packages, the manifest _is_ an integral part of your "API". Introduce a breaking change without bumping the major version, you'll garner the wrath of your customers and possibly loose credibility. Do that too many times, and folks will start treating your minor/patch changes as-if they are breaking changes. That could be disaster for you in the case where you must get a security fix out immediately. Just consider what it is you are putting that version number on and how it affects your customers. Except in the case of a single, stand-alone, dependency-free API; any API(s) that you ship in a package should be versioned separately from the package that contains them. If I pull your latest package and it's only had minor/patch level revision bumps, it better not break my build or product, or I will not be happy. Do that twice, I will complain loudly. By the third time, I will be dropping your product and complaining on every relevant forum I can find.
Author
Owner

@invalidred commented on GitHub (Mar 25, 2019):

@theetrain suggestion of bumping package's major version when peerDependency's major version is bumped or when a new peerDependency is added, makes sense but it does have one tiny caveat. Looking at long term view of packages say in tds-core or tds-community which have mono repo's with independently versioned packages. There is a high possibility of having components with variable major versions which will make development slightly awkward. For instance we will have following package versions:

  • tds-core/tabs - v3.0.0
  • tds-core/toggle-switch - v2.0.0
  • tds-core/something - v7.0.0

This will be bit challenging looking a the change-logs why major versions are way off. Also it would be nice to enforce @theetrain suggestion through some build tool that ensure that packages are bumped when peer-dependencies are added or have major version bumps to avoid versioning confusion.

<!-- gh-comment-id:476203523 --> @invalidred commented on GitHub (Mar 25, 2019): @theetrain suggestion of bumping package's major version when peerDependency's major version is bumped or when a new peerDependency is added, makes sense but it does have one tiny caveat. Looking at long term view of packages say in `tds-core` or `tds-community` which have mono repo's with independently versioned packages. There is a high possibility of having components with variable major versions which will make development slightly awkward. For instance we will have following package versions: - tds-core/tabs - v3.0.0 - tds-core/toggle-switch - v2.0.0 - tds-core/something - v7.0.0 This will be bit challenging looking a the change-logs why major versions are way off. Also it would be nice to enforce @theetrain suggestion through some **build tool** that ensure that packages are bumped when peer-dependencies are added or have major version bumps to avoid versioning confusion.
Author
Owner

@jwdonahue commented on GitHub (Mar 31, 2019):

@invalidred, why would the specific values of the nodes in the dependency graph be "challenging"? The package has it's own version that may in fact be part of a larger graph. There's nothing in-congruent with having a P.1.0.0 that has dozens of dependencies in the range of (Xmin.Ymin.Zmin, XVeryLarge.YVeryLarge.ZVeryLarge). When that package is born, it must contain references to it's dependencies at whatever point they are in their life-cycle.

<!-- gh-comment-id:478305011 --> @jwdonahue commented on GitHub (Mar 31, 2019): @invalidred, why would the specific values of the nodes in the dependency graph be "challenging"? The package has it's own version that may in fact be part of a larger graph. There's nothing in-congruent with having a P.1.0.0 that has dozens of dependencies in the range of (Xmin.Ymin.Zmin, XVeryLarge.YVeryLarge.ZVeryLarge). When that package is born, it must contain references to it's dependencies at whatever point they are in their life-cycle.
Author
Owner

@unrevised6419 commented on GitHub (Nov 29, 2019):

Situation 4: upgrading peerDependencies minor version

In a situation where a peerDependency's version is bumped to its next minor version:

package.json

{
  "name": "redux-form",
  "version": "8.2.6",
  "peerDependencies": {
-   "react": "^16.4.0",
+   "react": "^16.8.0",
  }
}

So we can increase react minor version, and release redux-form using minor version.

If user is accepting new minor versions automatically, both libraries should update.
If is not accepting automatic minor version, then both libraries should not update

There is only the case when user has react installed as a fixed versions.

What are your thoughts on this?
Thanks!

ref: https://github.com/redux-form/redux-form/issues/4317

<!-- gh-comment-id:559761695 --> @unrevised6419 commented on GitHub (Nov 29, 2019): ## Situation 4: upgrading peerDependencies minor version In a situation where a peerDependency's version is bumped to its next minor version: **package.json** ```diff { "name": "redux-form", "version": "8.2.6", "peerDependencies": { - "react": "^16.4.0", + "react": "^16.8.0", } } ``` So we can increase `react` minor version, and release `redux-form` using minor version. If user is accepting new minor versions automatically, both libraries should update. If is not accepting automatic minor version, then both libraries should not update There is only the case when user has `react` installed as a fixed versions. What are your thoughts on this? Thanks! ref: https://github.com/redux-form/redux-form/issues/4317
Author
Owner

@theetrain commented on GitHub (Nov 29, 2019):

Hi @iamandrewluca, from what I've learned, anytime you upgrade a peerDependency from any version, you would have to release a major version to the parent module.

In your example, redux-form has a peerDependency of react that upgrades from ^16.4.0 -> ^16.8.0. Since that changes the public API for consumers of redux-form, that would be a breaking change. redux-form would then need to bump as such: 8.2.6 -> 9.0.0.

It's a breaking change because consumers of redux-form that are using react@16.4.0 may not have enough functionality in order for the newest redux-form to work, potentially causing errors.

<!-- gh-comment-id:559836838 --> @theetrain commented on GitHub (Nov 29, 2019): Hi @iamandrewluca, from what I've learned, anytime you upgrade a peerDependency from any version, you would have to release a major version to the parent module. In your example, `redux-form` has a peerDependency of `react` that upgrades from `^16.4.0 -> ^16.8.0`. Since that changes the public API for consumers of `redux-form`, that would be a breaking change. `redux-form` would then need to bump as such: `8.2.6 -> 9.0.0`. It's a breaking change because consumers of `redux-form` that are using `react@16.4.0` may not have enough functionality in order for the newest `redux-form` to work, potentially causing errors.
Author
Owner

@unrevised6419 commented on GitHub (Nov 29, 2019):

@theetrain Thanks for explanation!

<!-- gh-comment-id:559839917 --> @unrevised6419 commented on GitHub (Nov 29, 2019): @theetrain Thanks for explanation!
Author
Owner

@jedwards1211 commented on GitHub (Nov 29, 2019):

@invalidred

it would be nice to enforce @theetrain suggestion through some build tool that ensure that packages are bumped

You could look into writing a plugin for semantic-release, the automated package publishing tool

<!-- gh-comment-id:559858718 --> @jedwards1211 commented on GitHub (Nov 29, 2019): @invalidred > it would be nice to enforce @theetrain suggestion through some build tool that ensure that packages are bumped You could look into writing a plugin for [`semantic-release`](https://github.com/semantic-release/semantic-release), the automated package publishing tool
Author
Owner

@jwdonahue commented on GitHub (Jan 15, 2020):

@theetrain, unless you have further questions, please close this issue at your earliest possible convenience.

<!-- gh-comment-id:574878336 --> @jwdonahue commented on GitHub (Jan 15, 2020): @theetrain, unless you have further questions, please close this issue at your earliest possible convenience.
Author
Owner

@theetrain commented on GitHub (Jan 15, 2020):

Thanks everyone for your insights. I learned a lot from this.

<!-- gh-comment-id:574880028 --> @theetrain commented on GitHub (Jan 15, 2020): Thanks everyone for your insights. I learned a lot from this.
Author
Owner

@SevenOutman commented on GitHub (Oct 23, 2023):

In my own words, unlike dependencies live as "implementation details" of a package, peerDependencies are "system requirements", just like whether a sofeware runs on Windows 10 or Windows XP. It turned out easier for me to examine the impact level with this in mind.

Situation 1: upgrading peerDependencies

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
-   "react": ">=15",
+   "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

This does not simply mean "I refactored part of my package" like when we update dependencies, but "this package now requires react ^16.7.0 to function, and no longer work with react >= 15 < 16.7.0". And that is clearly a breaking change now.

It also applies even if only the minor version of peerDependencies is upgraded (situation 4), which means "this package no longer work with react >= 16.4.0 < 16.8.0"

{
  "name": "redux-form",
  "version": "8.2.6",
  "peerDependencies": {
-   "react": "^16.4.0",
+   "react": "^16.8.0",
  }
}

Situation 2: adding peerDependencies

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
+   "styled-components": "^4.1.3",
    "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

This means "this package now requires styled-components to function, and no longer work where style-components is absent".

Situation 3: removing peerDependencies

{
  "name": "@tds/core-strong",
  "version": "1.0.5",
  "peerDependencies": {
-   "styled-components": "^4.1.3",
    "react": "^16.7.0",
    "react-dom": ">=15"
  }
}

This means "this package now works even if style-components is absent, which used to be impossible". It's a feature enlargement, and I think it should result in a minor version bump, just like when we add new APIs.

And I believe it also applies when peerDependencies version range is enlarged.

{
  "name": "redux-form",
  "version": "8.2.6",
  "peerDependencies": {
-   "react": "^16.8.0",
+   "react": "^16.8.0 || ^17.0.0",
  }
}
<!-- gh-comment-id:1774377496 --> @SevenOutman commented on GitHub (Oct 23, 2023): In my own words, unlike `dependencies` live as "implementation details" of a package, `peerDependencies` are "system requirements", just like whether a sofeware runs on Windows 10 or Windows XP. It turned out easier for me to examine the impact level with this in mind. ### Situation 1: upgrading peerDependencies ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { - "react": ">=15", + "react": "^16.7.0", "react-dom": ">=15" } } ``` This does not simply mean "_I refactored part of my package_" like when we update `dependencies`, but "_this package now requires react ^16.7.0 to function, and **no longer work with react >= 15 < 16.7.0**_". And that is clearly a breaking change now. It also applies even if only the minor version of `peerDependencies` is upgraded ([situation 4](https://github.com/semver/semver/issues/502#issuecomment-559761695)), which means "_this package **no longer work with react >= 16.4.0 < 16.8.0**_" ```diff { "name": "redux-form", "version": "8.2.6", "peerDependencies": { - "react": "^16.4.0", + "react": "^16.8.0", } } ``` ### Situation 2: adding peerDependencies ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { + "styled-components": "^4.1.3", "react": "^16.7.0", "react-dom": ">=15" } } ``` This means "_this package now requires styled-components to function, and **no longer work where style-components is absent**_". ### Situation 3: removing peerDependencies ```diff { "name": "@tds/core-strong", "version": "1.0.5", "peerDependencies": { - "styled-components": "^4.1.3", "react": "^16.7.0", "react-dom": ">=15" } } ``` This means "_this package now works even if style-components is absent, **which used to be impossible**_". It's a feature enlargement, and I think it should result in a minor version bump, just like when we add new APIs. And I believe it also applies when `peerDependencies` version range is enlarged. ```diff { "name": "redux-form", "version": "8.2.6", "peerDependencies": { - "react": "^16.8.0", + "react": "^16.8.0 || ^17.0.0", } } ```
Author
Owner

@xuhdev commented on GitHub (Jul 8, 2024):

I've also written an article on this:

<!-- gh-comment-id:2215206693 --> @xuhdev commented on GitHub (Jul 8, 2024): I've also written an [article](https://8hob.io/posts/semantic-versioning-npm-packages-after-dependency-update/#peer-dependency) on this: ![](https://8hob.io/posts/semantic-versioning-npm-packages-after-dependency-update/og.png)
Author
Owner

@ljharb commented on GitHub (Jul 8, 2024):

@xuhdev in npm, adding an optional dependency is semver-major as well, because someone could have a conflicting version already installed; additionally, bumping the minor or patch of a peer dep is also always a semver-major change.

<!-- gh-comment-id:2215443827 --> @ljharb commented on GitHub (Jul 8, 2024): @xuhdev in npm, adding an optional dependency is semver-major as well, because someone could have a conflicting version already installed; additionally, bumping the minor or patch of a peer dep is also always a semver-major change.
Author
Owner

@unrevised6419 commented on GitHub (Jul 8, 2024):

@ljharb, the argument they gave in favor of peer dependencies being a minor/patch is that the user, in the cause, can update those packages that theoretically are back compatible. The only exception when for the user in cause somehow those minor/patch updates may break something.

PS: also, the latest npm version should protect us from conflicting dependencies.

<!-- gh-comment-id:2215479780 --> @unrevised6419 commented on GitHub (Jul 8, 2024): @ljharb, the argument they gave in favor of peer dependencies being a minor/patch is that the user, in the cause, can update those packages that theoretically are back compatible. The only exception when for the user in cause somehow those minor/patch updates may break something. PS: also, the latest npm version should protect us from conflicting dependencies.
Author
Owner

@ljharb commented on GitHub (Jul 8, 2024):

@iamandrewluca the user can update anything with any breaking change - it's a breaking change if they have to do anything besides update your package. That includes all behavior of all versions of npm, including the latest one.

<!-- gh-comment-id:2215484893 --> @ljharb commented on GitHub (Jul 8, 2024): @iamandrewluca the user can update anything with any breaking change - it's a breaking change *if they have to do anything* besides update your package. That includes all behavior of all versions of npm, including the latest one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/semver#4615