Surgically upgrading yarn managed Javascript packages

Upgrading just a single library in Javascript might appear to be a trivial task. But there are a few non-intuitive elements that make it a bit more complicated.

Surgically upgrading yarn managed Javascript packages
Photo by Wilhelm Gunkel / Unsplash

I found myself having to update a few Javascript libraries from a big project. These libraries were actually not directly included by the project itself, but rather they were transitively included; that is, they were not in the package.json file.
Deleting and rebuilding the yarn.lock was out of the question as it would change way too many things and I'm not the project's owner and I wanted to keep the changes to a minimum. I needed to find a way just update those few libraries and nothing else.

The first thing I noticed was that there were several versions of the same library, for someone who's always worked with C++ it is kind of a shocker to see this. Not judging here if it is good or not, but wow it gets messy so quickly.

What I found most interesting is that, upon a closer look at the yarn.lock file, there were multiple references that could have been collapsed into one. For example a@^1.2.3 and a@^1.2.4 would resolve to a@1.3.5 but a@^1.3.7 would resolve to a@1.4.0.
Based on the git blame of when each reference was introduced, I have to assume that yarn will keep a sort of geological layers of library references and resolved versions. If a new reference is added it is resolved to the latest one at the time of inclusion but will not update any of the locked ones even if it could.

After this preliminary work to understand the situation, I started trying to upgrade using yarn upgrade but was failing to get rid of the old versions. It took me a few attempts to understand what was happening: I was just adding a new layer leaving the rest unchanged. For example:

jquery@>=1.7, jquery@^3.5.1:
   version "3.5.1"
   ...

After running yarn upgrade jquery the result is:

jquery@>=1.7:
  version "3.5.1"
  ...
 
jquery@^3.5.1:
  version "3.6.0"
  ...

This is not the expectation I had, I was hoping to see every reference to jquery being updated to the latest one.

The way I found to successfully achieve my objective was to execute yarn upgrade once with each of the different patterns that were present in the yarn.lock file for the library. In this example it would be: yarn upgrade "jquery@>=1.7" followed by yarn upgrade "jquery@^3.5.1". Trying to use multiple in a single command wouldn't work either.

After doing that, the yarn.lock will clearly state that all the patterns for the library are pointing to a single version, the latest at the time the commands are executed.

But there's one more caveat. This will add the library to the package.json and that is something I was not interested in doing as it is not a dependency of the project per-se. Thus, the file needs to be restored before commiting the changes.

A few lessons learned here for a JS newbie like me:

  • yarn will create a geological layers of version of the same library if not carefully upgraded
  • The yarn upgrade command has some non-intuitive behaviors
  • Always inspect the yarn.lock file as it is the source of truth to the specific versions of the libraries being used

Comments powered by Talkyard.