In this tutorial, you will learn how to master debugging issues within your project that are related to packaging mismatches. Currently, I am working on a large React.js monorepo. This application is fairly big and the total package count must be around 200-300 packages in total. All these packages are split into different applications. Some are shared, some are unique to the application itself. The application has 20-30 developers working on it, all split into many teams. Having such a large team with such a large amount of changes can make life interesting. One of the biggest areas that leads to bugs when working locally is package mismatches. In this guide, I will share all the tips and tricks I've learned in order to debug and figure out exactly what versions of a package an application architecture like this should be using.
Packages and Versions - The Basics
First, let's start with the basics. Let's say you have a package.json
file with this reference:
The first thing you may notice as odd about this reference is the caret, ^
. The reason the caret has been added to the package version number is to save time. Manually having to bump and commit every single package change is extremely tedious. If you work in a big repo keeping packages up-to-date is a chore, especially when your application is large. The caret ^
helps make managing these dependencies easier. The caret helps to automate this by auto-bumping package versions for you following the rules of semvar
In the example above, the caret before 1.0.1
means that whenever you run a package install using yarn
something clever will happen. When the caret is added yarn
will search and use the most recent minor version (the second number) for the specified major version (the first number) that is available for that package. In the example above, ^1.1.1
will match any version of that package within the 1.x.x
range, e.g. 1.2.0
and 1.3.0
. If a version 2.0.0
of your package exists, the caret will do nothing and you will have to manually bump it.
Another shortcode you can use to auto-bump packages is the tilde, ~
. The tilde will match the most recent patched version (the third number) for the specified minor version (the second number). In our example, ~1.1.1
will match all 1.1.x
releases. It would not match on 1.2.0
.
Issues With
Using the tilde or caret makes life easier for managing package updates, however, on occasion, it can also mean that things magically break. For example, say someone forgot to version an internal package that is used in the monorepo correctly. Someone has introduced a breaking change. This change should have been updated as a major version change, however, it has not been versioned correctly. The next time your application is published it magically breaks for no reason. In a perfect world, if semvar is followed correctly this should never happen, however, we live in the real world where people make mistakes. If your application suddenly breaks for no good reason, the first thing I suggest you do is have a look in the yarn.lock
file.
The yarn.lock
file is definitely your friend when it comes to debugging your application and figuring out exactly what versions of packages your application is using. One big tip is to never assume you know what version of a package your application is currently using. Always check your yarn.lock
file first. From the example listed above, if I look within my yarn.lock
for my application I would expect to see an entry like this:
If we dissect the yarn.lock
file we can see that my-package
has several of its own dependencies, specifically 'query-string'. If querystring
was updated but the update is not applied correctly everywhere it is referenced - both directly and indirectly - your application could misbehave in unexpected ways. If your application is fairly simple and does not use internal packages, you might be thinking this has never happened to me! To reiterate this advice is targeting developers who work in large mono repos, as this can be a really big problem. If you know that something has been updated in your code-base, checking the exact version that a package uses should be one of your first go-to actions when trying to debug your application. I have made this mistake countless times. I thought the application was using the latest and greatest version of a package, only to check my yarn.lock
to find out otherwise. yarn.lock
. If you start using the tilde and caret, then you can not use package.json
as the source of truth for your package versions. The most useful property within 'yarn.lock' for debugging package mismatch errors is the 'version' property. version
defines the exact version of that package your application is using.
Debugging package mismatch errors can be hard. In a lot of instances, you have to use common sense to help narrow things down. If you have an internal component called checkout
and someone reports a checkout page error, you can use some common sense to determine where to start the hunt. Checking source control to see the latest release number for a package, or, checking the commit history for the version your application is referencing can help identify bugs.
The other thing when debugging package related issues within yarn.lock
file is to make sure there are no duplicate entries for your package. There can be numerous causes for duplicate entries. If you commit your yarn.lock
into source-control, one potential cause can be down to a bad merge. This can really screw up your day. I've encountered situations where I've needed to debug some code I've never looked at before. A manual update of a package did not work. After figuring out it had a duplicate entry in yarn.lock
I completely deleted it and recreated it. This fixed the issue but wasted a lot of time. Another pro tip is to make sure the package that is causing an issue is still used before fixing it. I have also wasted time trying to fix a mismatch only to realise the package in question was not being used anymore and no one had tidied up the config 😢
Updating Packages
There are two main ways of updating a package. The simple way is to update a version using upgrade
command. This way is OK but it won't magically auto-bump any sub-packages that have the tilde or caret.
A better way to update your packages is to use the upgrade-interactive
command. upgrade-interactive
will also update all the sub-dependencies as well. This command is used like this:
When you use yarn upgrade-interactive
you can pick what packages within your application you want to update. If you follow these tips then I promise you will start to diagnose your application in a lot less time. Happy Coding 🤘