Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

How peerDependencies affect package versions #502

Closed
theetrain opened this issue Feb 22, 2019 · 15 comments
Closed

How peerDependencies affect package versions #502

theetrain opened this issue Feb 22, 2019 · 15 comments

Comments

@theetrain
Copy link

theetrain commented Feb 22, 2019

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)

@FichteFoll
Copy link

FichteFoll commented 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.

@FichteFoll
Copy link

Related: #444, #337, #341

@ljharb
Copy link
Contributor

ljharb commented 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.

@segiddins
Copy link
Member

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?

@ljharb
Copy link
Contributor

ljharb commented Feb 23, 2019

Dependencies are transparent, and aren鈥檛. Peer dependencies hoist, and are.

@jwdonahue
Copy link
Contributor

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.

@invalidred
Copy link

invalidred commented 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.

@jwdonahue
Copy link
Contributor

jwdonahue commented 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.

@iamandrewluca
Copy link

iamandrewluca commented 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: redux-form/redux-form#4317

@theetrain
Copy link
Author

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.

@iamandrewluca
Copy link

@theetrain Thanks for explanation!

@jedwards1211
Copy link

@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

@jwdonahue
Copy link
Contributor

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

@theetrain
Copy link
Author

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

dschaller referenced this issue in react-hook-form/devtools May 6, 2020
foolip added a commit to w3c/webref that referenced this issue Apr 16, 2021
This major release is because the webidl2 peerDependency was bumped:
#181

See semver/semver#502 for precedent.
foolip added a commit to w3c/webref that referenced this issue Apr 19, 2021
This major release is because the webidl2 peerDependency was bumped:
#181

See semver/semver#502 for precedent.
foolip added a commit to w3c/webref that referenced this issue Apr 19, 2021
This major release is because the webidl2 peerDependency was bumped:
#181

See semver/semver#502 for precedent.
foolip added a commit to w3c/webref that referenced this issue Apr 19, 2021
This major release is because the webidl2 peerDependency was bumped:
#181

See semver/semver#502 for precedent.
@SevenOutman
Copy link

SevenOutman commented 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",
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants