Give your outdated libraries some respect

Nuno Cruz
Feedzai Techblog
Published in
6 min readJan 23, 2019

If you work in the JavaScript ecosystem, you are most likely aware of how difficult it is to manage dependencies. However, justifying the importance of having enough time and resources to update your project’s outdated libraries to your manager can be even more challenging.

Of course, not all libraries require the same amount of effort in order to be updated, nor do they have the same level of importance for a project. For instance, frameworks that support your application (React, Vue.js, etc.), or tools that are the foundation of your development setup (webpack, babel, etc.) have a major impact.

In this blog post, I will share the lessons I’ve learned by not dedicating enough time to outdated libraries.

Treat a major upgrade as a new product feature

When one of your core libraries releases a major version, it is time to start thinking about upgrading. The first questions that should come to mind are: “How mature is this version? Does it have several open issues? Is it stable enough to be used?”. Being an early adopter of a new library version always comes with some risks, mainly because bugs are common in new versions, that need time to be fixed. However, if time constraints aren’t a concern, being an early adopter is an excellent opportunity to contribute to open source projects. It allows you the chance to test any new versions first-hand and potentially provide solutions to any bugs you encounter. However, if time is a constraint, it is most likely better practice to wait until the library becomes more mature, in order to avoid dealing with potential bugs.

Whether you are an early adopter or not, the next step is checking the upgrade guide in order to estimate how difficult the upgrade will be from one version to the next. If the upgrade isn’t particularly challenging, you can most likely upgrade the library in a couple of hours without worrying about a potential impact to you and your team.

However, problems arise when a dependency has several breaking changes and issues can become even more complex if other dependencies also require upgrades. An example of this was the migration from React 15.6 to React 16.4. Besides breaking changes, several other dependencies (such as react-router) required upgrades since the new React version wasn’t compatible with existing dependencies. In this scenario, you should stress the importance of prioritizing this to your manager.

So far everything is going well until you realize that your task hasn’t moved in the backlog. Roadmaps are commonly full of new features, and we easily make the mistake of ignoring tasks deemed less important, such as upgrading sensitive dependencies, in order to give new features all of the attention. These features are often prioritized because, at the end of the day, these features make clients and users happy. If this is a common pattern in your team, it is your responsibility to speak up and make others see the importance of upgrading dependencies.

Here are the main reasons why upgrading libraries are important:

  • 🚀 New features: Most dependencies have a clear roadmap for new features. For example, in React 16.0 they added native support for the concept of Portals. In previous React versions, this concept was only supported by third-party libraries like react-portal.
  • 🏎️ Performance improvements: In addition to new features, performance improvements are also made frequently.
  • 🐛 Fixed bugs: Although it is normal to find bugs in libraries that you depend on, patch releases help fix these issues.
  • 👮 Security patches: Security patches are one of the most important reasons to have your libraries up-to-date. Are you familiar with the Equifax security breach? TL;DR, the Equifax hack was possible because of an external library the company relied on, which had a gaping security flaw. The external library itself received a patch to fix the issues, but Equifax never updated the internal code used in their systems. This was missed despite the fact that the library team knew of the vulnerability and even released a security update.

Don’t leave the upgrading process in the middle

This might sound odd, but you should not leave an upgrade in the middle of an upgrade process. Why is this important? I’ll explain with an example that occurred recently in our team.

When we finally allocated resources to upgrade from React 15.6 to React 16.4, we were simultaneously developing three major new features for our product. The React migration took us roughly a week and a half, but since we were close to the release date and needed more time to ensure we didn’t break anything, we decided not to include the React migration in the upcoming release.

Meanwhile, the new release brought more important features that needed to be addressed. Since these features were critical, all of our resources were allocated to developing them, leaving us no time to continue testing the React migration. When we finally had time to return to testing, the branch where we were performing the migration was significantly behind the master branch. When we rebased it, we discovered several issues. To fix these problems, we spent an additional week on the process since several changes were needed. As a result, the migration was delayed and more work was needed than initially planned.

The lesson learned is that if time is limited and resources scarce, don’t leave the process in the middle. Delaying the process might require extra time to complete in the long run.

If something breaks, fix it instead of rolling back to the latest working version

Have you ever come into work to find your builds failing after installing a new version of an external library?

If so, the common fix is to revert and lock to the previous working version. This is done to ensure that it won’t automatically update to a newer version and possibly cause builds to crash again.

Although this approach might be a good short-term solution because it fixes the builds and the work can continue, this approach is actually only sweeping the dust under the rug.

In the long run, adopting the approach described above will have a negative impact on both the codebase and the team. The codebase will be impacted because future updates will be missed that might contain important patches or security fixes. The team will be affected because an individual will have to update a set of libraries that are more time-consuming and error-prone than updating one at the time.

A strong takeaway from this is that if an application crashes because of a library update, take the time to fix it as soon as possible. A good solution to this is to temporarily rollback the library to the previous compatible version and ensure the builds are working while fixing the problem in a side branch. Although this takes more than a day to resolve it will still be less time in the long run than if it is delayed in the future.

Don’t wait for authors to upgrade their libraries

Imagine that a library named X has a peer dependency on React 15.6 and you are migrating your application to React 16.4. You’ll probably get the following error message:

npm ERR! peer dep missing: react@15.6.X, required by X@0.0.0

I’ve seen many projects where the solution to this problem is to simply fork the library, update its peer dependency and update the package.json file to use the forked version. Please don’t❗❗❗️

This approach is bad behaviour and should be avoided at all costs. Many developers face this problem. In the long run, you end up shooting yourself in the foot because it will be harder to upgrade the library. Rather than putting yourself in a difficult position, you can view this as an opportunity to contribute to open source projects.

Note: While waiting for the library author to accept your pull request, you can temporarily update the library version in your package.json to use the branch that you used to make the PR. (See: “How to install an npm package from GitHub directly?”).

Set a routine to warn you when a library releases a new version

Staying up to date with a never-ending stream of releases, and testing each one’s compatibility with your application is a time-consuming task for a developer. To avoid this, there are tools such as Greenkeeper that can help you automate this job. This way, you’ll never miss a library update and can easily check whether that library can be easily integrated into your project.

Final thoughts

Maintaining an application with a large codebase requires a lot of discipline and having your dependencies up to date should be a top priority for your team and organization. Not making this a regular practice will slowly make your codebase more difficult to maintain which hinders your team’s productivity and satisfaction.

--

--

Nuno Cruz
Feedzai Techblog

Eng. Manager @ Sky || Web team - Streaming Platform 📺🚀