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.
The reasons why a developer might abandon a package are manifold. They may have lost interest, stopped using the package themselves, or even stopped working with JavaScript altogether.
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.
But it doesn’t have to be that way. You can avoid many of these pitfalls by implementing some handy tools and workflows to handle the lion’s share of maintaining and future-proofing your JavaScript libraries.
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.
Let’s get the most obvious point out of the way: to keep anything up to date, you must be knowledgeable about its environment.
Keeping up with the JavaScript ecosystem is a tall order. There’s more new, interesting stuff popping up every day than you could ever fit into your learning schedule. Don’t blindly follow every new and shiny trend, but keep a keen eye on things that come up time and time again.
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, async
/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.
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.
Make no mistake: keeping JavaScript libraries up to date always involves change. Change is scary because the outcome is often uncertain. If you don’t have automated tests in place, you’re much more likely to avoid updating things because you’re concerned you’ll break something without noticing. Been there, done that.
So what should you do?
There’s a plethora of libraries for testing JavaScript code. If you’re not sure which one to pick, I’d recommend using Jest. It’s hugely popular, easy to get started with and, being developed by Facebook, has strong corporate backing.
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.”
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.
If you write something for the JavaScript ecosystem, it’s very likely that your code depends on a number of packages hosted on npm. At the very least, you have a dependency on your testing framework.
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.
Luckily, keeping dependencies up to date can be automated as well. Like anything in the JavaScript universe, there are multiple tools available to get the job done. The most obvious choice, since it’s integrated with GitHub, is Dependabot. Dependabot opens pull requests in your GitHub repos, one per dependency to update. Your CI service runs all your tests against the changes Dependabot makes. If tests don’t pass, it’ll be easy to root out the problem because you’ll know exactly which dependency update caused the tests to fail.
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.
JavaScript is a dynamic language, and its code is very discoverable. This is especially true for libraries created for use in the browser since they often put everything they do onto a single global object. If you leave users in the dark about the functionality of your library’s API, they will figure it out themselves — and they’ll likely find and use things you never intended them to. This will make your code a nightmare to maintain; if your users are relying on your library’s internals, there’s no way for you to change them without potentially breaking your users’ 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!
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.
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.
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.
npm publish
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.
There are many techniques we did not cover; it would be impossible to pack everything into one article. However, the tips above should give you a solid foundation to keep your JavaScript libraries up to date without any of the headaches usually associated with the task.
Let’s recap what we learned:
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.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.