Bumping the major version of your Javascript library is user hostile

I was surprised this week to discover my employer’s medium sized typescript monorepo was using eight distinct versions of glob across five major version lines1. glob has transitives, contributing to the repository also having seven distinct versions of minimatch 2 and three distinct versions of brace-expansion3. We only have a single version of slonik, but it’s gone from v33 to v49 over three years and the upgrade path between them is not simple.

Between them, these packages have had dozens of Github security alerts in our repository over the past 12 months. Mostly ReDOS and vulnerability classes that are not exploitable in our specific use case, but nonetheless trigger compute to try updates and consume valuable engineer time to triage. Even worse, how many thousands of open source maintainers have burnt time and money dealing with requests to bump dependency constraints to resolve security alerts?

The original sin was node building a dependency system that makes breaking changes appear cheap by supporting multiple versions of each dependency at the same time. Sadly that ship has long sailed.

I’m begging maintainers of javascript libraries: bump the major versions on your packages as infrequently as you can. Allow your users to consolidate on fewer distinct versions of your package, and reduce the upgrading, triaging, and reviewing you’re externalising onto them.

It’s true that semantic versioning permits breaking changes in a major version bump. However, that doesn’t obligate you to make breaking changes. What if you … didn’t? Not forever, just for much longer. Even limiting major version bumps to once every 2-3 years would be an improved user experience.

Can some changes be additive only, expanding the API of your library and deprecating (but not removing) old features? Do you really need to remove support for older node versions annually? Is removing active CI and compatibility guarantees for older node versions even a breaking change? Do you really need to change the default exports of your ESM package right now?

Additionally, library maintainers can help insulate their users from the insanity in transitive dependencies and reduce their own maintenance burden:

Finally, a shout out to some high profile packages that get this right, with many minor versions and few (if any) major bumps over recent years:

Postfix

If you’re a specification fan, semver.org has you covered:

Q: If even the tiniest backward incompatible changes to the public API require a major version bump, won’t I end up at version 42.0.0 very rapidly?

A: This is a question of responsible development and foresight. Incompatible changes should not be introduced lightly to software that has a lot of dependent code. The cost that must be incurred to upgrade can be significant. Having to bump major versions to release incompatible changes means you’ll think through the impact of your changes, and evaluate the cost/benefit ratio involved

  1. [ "7.1.7", "7.2.3", "8.0.1", "8.1.0", "10.5.0", "11.1.0", "13.0.0", "13.0.6" ] 

  2. [ "3.0.8", "3.1.5", "5.1.9", "7.4.9", "9.0.9", "10.1.1", "10.2.6" ] 

  3. [ "1.1.18", "2.1.4", "5.0.9" ]