Jahed Ahmed

Dependency Hell ft. Yarn v2

One of the things I do every week is upgrade all of FrontierNav's dependencies to make sure things don't go out of date and become a pain to fix later on. In FrontierNav's last weekly, I mentioned how I "organised build configuration". For example, I moved my eslintrc.json to its own eslint-config package which I extend from and I moved all relevant dependencies to reduce the clutter in my root package.

This week, it turns out doing that triggered a bug in Yarn v1 where it gets confused about internal dependencies. I think it's this bug, but there's a lot of similar ones. A rollback to an older version of Yarn fixed that issue, but it caused me to waste a lot of time figuring out the problem, which I still don't know the root cause of.

My initial attempt was to upgrade to Yarn v2, which I avoided as it overhauled NodeJS's dependency resolution and just looked like a giant bundle of workarounds and hacks. I gave it the benefit of the doubt, but I hate to say that I was right in my impressions. Yarn v2 may provide smoother dependency management, but the amount of caveats and things to learn on top of NodeJS's systems is ridiculous. Sure you can just follow the installation docs and be ignorant to what it does (i.e. "magic"), but there's this constant feeling that your entire project is built on a house of cards.

When using Yarn v2, you need to know that you're using Yarn v2 whenever you're dealing with any dependency resolution, including your own IDE. That's on top of Babel, Webpack and NodeJS which do their own resolutions. I learnt a long time ago with Webpack, not to get too smart with dependency resolution, for the sake of long term maintenance and compatibility with tooling, so it's a major red flag for me when something veers off from NodeJS's defaults, which itself is now having to figure out compatibility with JavaScript's import statement.

I ended up taking a good look at my collapsed house of cards and rolled it back to the house of cards that was working before. That is, an older version of Yarn that didn't fall apart yet. I basically gave up. Which sucks.

Was I doing something wrong here? I looked at some projects that use Yarn v2 and I can safely say, no. Take for example Babel, one of the cornerstones of JavaScript development. They use Yarn v2 and have a dedicated "patches" folder to work with Lerna, which is another dependency management tool. Oh no.

TypeScript and JavaScript shouldn't need all of this complexity. I should just be able to:

import { thing } from "https://example.com/[email protected]/module.ts";

Which is a Web Standard now. No manual package.json maintenance needed.

Deno does this but it's an entirely new runtime so none of the existing NodeJS tooling, like Webpack, will work on it. Deno and Yarn v2 are similar in that way. But while Yarn v2 tries to keep existing tooling working by adding more on top of it, Deno does away with all of it to provide a consistence experience.

Deno has been on my radar for years now, but I can't afford to jump onto a somewhat niche platform. Is it maintained by hobbyists? Does it have enough funding? I don't know. It has a lot of its own caveats since it's so new. So I guess I'll continue waiting until it becomes more popular and irons out a few more features or until something better arrives.

Thanks for reading.