npm is a wild place. It’s the largest package registry on the planet by a long shot, and its growth rates are beyond anything the world of package managing has ever experienced. Most of the packages there have not been updated in years.
In my experience, people commonly fail to keep their code up to date simply because it can be boring, exhausting, tedious work. Will the package keep working if I add this feature? Would updating that dependency break anything? Which of my dependencies are outdated, anyway? This line of internal questioning can quickly grow tiresome.
In this guide, we’ll outline five techniques to help you minimize the headaches associated with keeping your libraries up to date. We won’t dive into too much detail on how to set them up — that’s beyond the scope of a single article — but we’ll point you toward some helpful resources if you want to learn more. Depending on your experience level in the open source publishing space, some of these techniques may seem new and daunting. But I promise it’s worth your time, especially if you’re maintaining multiple libraries.
1. Stay on top of things
Let’s get the most obvious point out of the way: to keep anything up to date, you must be knowledgeable about its environment.
A lot of the overall change in the JS ecosystem is driven by updates to the language itself. We see groundbreaking, can’t-miss improvements — such as Promises,
await, the ES module syntax, and more — every few years. Axel Rauschmayer’s 2ality blog, which periodically breaks down new and upcoming features to the ECMAScript standard, is a good resource for tracking these changes.
But understanding the outside world is only one side of the coin. The other is knowing the inside world — your library — like the back of your hand. This might seem obvious, but if you don’t use your library yourself — or don’t use it anymore — you’re likely not to notice pain points that users commonly encounter. You’d never know it, for instance, if your users were having to fight through confusing callback code instead of using Promises, or seeing repeated alerts every time they use your library because it uses a long-outdated Node.js API.
Bottom line: if you want to keep your library up to date, use it yourself. That’s how you’ll notice opportunities for updates.
2. Write automated tests
That you should write tests is probably one of the most well-known and least controversial requirements in open source work. Good tests both ensure that your software works as intended and reduce the likelihood that future changes will break your code.
So what should you do?
Pick a testing framework
Write good tests
Learning how to write good tests is actually more important than picking a particular framework. But if you have no idea how to write tests, you may want to start by diving into the adventure of writing tests and then build up some experience from that.
There are a lot of paradigms for writing good tests, and certain people will probably oppose you regardless of which one you choose. I, personally, subscribe to Kent C. Dodds’ approach: “The more your tests resemble the way your software is used, the more confidence they can give you.”
Automate your testing
Continuous integration (CI) is a process that automatically runs tests against your code whenever it changes (i.e., whenever you push it to GitHub). There are myriad providers that offer CI services, most of which are free for open-source software. When I first set up a library with continuous integration some years ago, I picked Travis CI and have been happy with it ever since.
3. Monitor your dependencies
To keep things fresh and secure in your library, you’ll have to make sure your dependencies are up to date. Sure, you can do that manually (with a tool such as npm-check). But, just as with testing, if you have to do annoying stuff manually, there’s a good chance you won’t do it at all.
4. Document your public API
I may lose some of you here, but I’ll say it anyway:
Write 👏 a 👏 comprehensive 👏 README.
I can’t overstate how important it is to document your library’s public API. Extensively.
This is the type of work that most developers hate from the bottom of their hearts. But high-quality documentation can boost adoption, bolster your professional reputation, and help users determine how to best use specific parts of your code.
Besides writing a README, another great to document your library’s public API is to provide type definitions alongside your code. Type definitions help users discover your API, and it’ll prompt their code editors to warn them when they try to use anything that’s not declared as public in the types.
You don’t have to write type definitions manually; recent versions of TypeScript are clever enough to extract a definition file from JSDoc comments in your code!
5. Maintain your sanity when releasing
Pushing a new release of your library to npm is as easy as running
npm publish. It’s almost too easy — unforeseen problems are known to arise.
Here are some tips to help you stay organized, calm, and collected when releasing new versions.
Restrain yourself from releasing major versions
Releasing major versions is a necessary evil. You’ll need to do it now and then because you can’t maintain compatibility with every old thing forever. However, you’ll want to keep a low profile when it comes to publishing breaking changes, especially if your library has a nonzero amount of users. Collect your ideas for API updates someplace handy and bundle them into one major release when the time is right.
The reason for this is simple: minor and patch updates are usually installed automatically when
npm install is run in a project, which will happen from time to time in most active projects. However, the odds of somebody actively updating your library across major version boundaries are relatively low. This leaves your users with hopelessly outdated software since you’re unlikely to port back features and bug fixes to previous major versions.
Drop support for outdated Node.js versions early
Just trust me on this. I’m guilty of maintaining support for old Node.js versions in some of my libraries, and it’s painful — mostly because even if you continue to support them, your dependencies will start dropping support for those old Node.js versions, and you’ll no longer be able to update them to their latest versions.
To make your life easier, drop support for Node.js releases that are no longer maintained whenever you do a major version bump.
As mentioned above,
npm publish makes it too easy to make mistakes. If you forget to commit a local change, bump the package version number, run tests, etc.,
npm publish will gleefully ignore these oversights.
A tool such as np will catch most of these problems and give you more confidence that things will still work as expected following a release. If you want to get really nerdy, you can even automate your releases.
Let’s recap what we learned:
- Never stop learning. If you don’t move forward, you’ll go backward. That’s how things work in the fast-moving world of tech in general, and on the web in particular
- Put things into writing. Until you set up public documentation, your concepts and their limits will remain a mystery to the masses. Make them known before your users take control over the interpretation of your API
- Automate, automate, automate! CI is your friend. No matter what the task, if a robot does it for you, it’ll save you a lot of time and stress and reduce the room for error
If you’re feeling overwhelmed, don’t fret: this is years of experience distilled into a short blog post. Rome wasn’t built in a day. If you integrate these learnings step by step, you’ll quickly build up confidence and develop a routine to keep your libraries up to date for the long term.
Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.