← More gists
Published

Keeping dependencies up-to-date

When you're using many dependencies, keeping them up-to-date can be a real challenge. Installing dependency updates can take a lot of time, time that isn't spent furthering business goals. Because of this, projects often fall behind on dependency updates.

Never updating dependencies is not an option. Sooner or later you have to update. It may be due to a security update, bug fix or new feature you need, performance improvements, or because you want to update another dependency, and there is an interconnection and the only way to update one is to update them all.

Putting off on updates for too long only makes it harder and harder to update, until eventually you'll be behind so much, actually doing it would take so much effort, that it becomes inconceivable.

Many legacy projects have been replaced by rewrites in big part because of this.

So then what's the compromise?

Automatic updates

There are solutions like Dependabot that automatically create a PR every time there is a new version of a dependency.

Picture: The GitHub profile card of the Dependabot account; Automated dependency updates built into GitHub.
Dependabot's profile on GitHub

But unless you have a perfect E2E test suite and you dare merge these updates without extensively testing everything they touch, this doesn't really solve anything. The time-consuming part of dependency updates isn't bumping the number in the package.json. Verifying the update doesn't break anything, still takes a lot of time and effort.

And since Dependabot will nag about every, single, update that is published, you may actually spend more time updating than if you did it manually.

There's also the question of whether you should update to the latest version of packages as soon as possible. If a package has only just been released, it may have brand new bugs, performance issues, or worse, deliberately introduced malicious code. It's safer to let new versions mature a bit before installing them.

Aside

The versioning scheme semver is often used to indicate the impact of an update. Just know that it's completely optional for a library to use the versioning scheme. You can't blindly trust version numbers.

Manually updating

A better way to keep dependencies up-to-date is to schedule some time periodically to do dependency updates. An hour a week is usually enough to catch up on dependency updates and then keep them up-to-date.

Picture: Shows the calendar view of the 26th of April 2024, a Friday. At 9:45 "Standup" is scheduled, followed immediately by "Dependency updates" at 10:00.
Put it in your calendar

An important benefit of this process is that it minimizes the amount of disruption. The limited time only allows you to update a few dependencies each week, making it much easier to identify dependencies causing new problems.

Manually updating also gives you much more control over when you update a dependency. This allows you to let new versions mature a bit before you adopt them. As mentioned before, this reduces the chance you introduce new issues or security vulnerabilities.

Tip

Before updating a dependency, make sure the dependency is still in use. The best kind of dependency update is one where you can remove the dependency entirely.

Prioritizing

When you limit the time spent on updating dependencies, and when there are many dependencies to update, it helps to be able to prioritize them.

Dependencies listed in critical security advisories (that are actually applicable to your app), should be updated as soon as possible. Do not delay those until your periodic update moment, not only does this put you and your users at risk, but it may also be considered negligence if things go wrong. Use automatic vulnerability scanning tools to identify security advisories applicable to your project.

Anything else (i.e. any non-critical dependency update) is basically a "nice to have". This means the order in which you update dependencies is not particularly important.

I usually start by processing the list of non-critical security advisories. Even though the vulnerabilities are non-critical, I think it's a good practice to try to keep the list empty. A nice bonus is that this list is usually much shorter than the list of outdated dependencies, which gives me an intermediate point to achieve.

Libyear

Once you have run out of other reasons to prioritize dependencies, I recommend using the libyear tool. Libyear is a simple measure of dependency freshness. It calculates how old each outdated package is compared to the release date of the most recent version. This gives you a nice list to work with, allowing you to prioritize the most outdated dependencies.

Another nice thing about libyear is that it makes dependency up-to-dateness quantifiable. By summing up the libyears for all dependencies, we get a number representing the project's up-to-dateness. The libyear website has a nice cartoon to illustrate this:

Picture: Terminal output showing install scripts found in a front-end project. There are some strange entries (ljharb-monorepo-symlink-test), and esbuild is shown.
Source: libyear.com

While it's a very simplified view, this also makes it very easy to work with. I think setting a max libyear-age can serve as a good directive. Keeping every dependency under 2 libyears-old (in addition to prioritizing vulnerabilities), would be a good place to start for projects in active development. An entire year to install a new version should be plenty of time without wasting time on updating too often.

Interested in running libyear on a npm/yarn project? Run it (safely in a Docker container) with:

docker run -v "$PWD":/usr/src/app -w /usr/src/app node:20 npx libyear
Aside

The output of the npm/yarn version of libyear looks a lot more complicated than it is. Just focus on "drift"; the amount of years (i.e. libyears) between when the current version you use was released, and when the latest version was published. The other metrics are much less significant.

Conclusion

While it can take up some of your available time, keeping dependencies up-to-date is crucial for the security and longevity of software projects.

Start the dependency updating process right now; create a dependency updates event in your calendar. In the first iteration, set up the tools you need to keep your dependencies up-to-date (especially dependency vulnerability scanning) and start by updating the most important dependency.

Once caught up, dependency management won't take nearly as much work. It's a relief how easy it is to update packages when it's only a small jump from the previous version and you don't also have to update numerous interdependencies.

Just keep at it.

More like this