Developing JavaScript libraries and avoiding dependency hell

Lewis Nelson
3 min readFeb 21, 2020
Praise be to yarn workspaces

TL;DR

Don’t use npm link when developing JavaScript libraries, use yarn workspaces instead. You’ll save countless hours of banging your head against a wall.

I’ve been working with JavaScript for around 6 years now and using npm for around the last 4 years. I’ve known about yarn for a couple of years now, but never felt the need to use it. I’ve never had any real problems with npm until this week.

We’ve recently reached a point of maturity in our front-end framework architecture at work and decided now would be a good time to break out a lot of our core code into separate libraries. The main idea is to make it easier to maintain our codebases. We share a lot of the same code across around a dozen code bases. I’ve created npm libraries before and never had too much trouble with them, other than private packages installed via GitHub, but we’ll save that for another day.

The idea is to have a core library of React components, then another library which consumes these core components to build out some more opinionated re-usable components. Finally both libraries will be consumed by our websites. No problem.

Problem. npm link is terrible. I tried all sorts of combinations of dependencies and peer dependencies, yet always ran into errors on build along the lines of react cannot be found or unable to import A from './a' etc. After many hours of banging my head against the wall and Googling for answers I gave up. I started looking for other solutions, surely I can’t be the only person to find npm link extremely frustrating.

One of the first tools I came across was Lerna (https://github.com/lerna/lerna) - “A tool for managing JavaScript projects with multiple packages.” Perfect! It seemed to do what I wanted it to do, but it was a fairly hefty package that seemed more geared towards mono-repos. Our setup was using individual repositories. I also didn’t want to waste any more time in case it didn’t work out. I done some more digging and came across yarn workspaces.

I have to admit I was skeptical at first, I’ve never understood why people use yarn over npm. After all they both have lock files and manage the dependencies from the same sources 🤷‍♂️. Nonetheless I followed the documentation (https://classic.yarnpkg.com/en/docs/workspaces/) and ended up with a directory structure along the lines of this:

/<root>
|
-> /package-a
| |
| -> package.json (name: "@org/package-a")
|
-> /package-b
| |
| -> package.json (name: "@org/package-b")
|
-> /site
| |
| -> package.json
|
-> package.json (root workspace)

package-a is the core library, package-b is the more opinionated library and site is the website repository.

package-a‘s package.json file looks something like this

{
"name": "@org/package-a",
"version": "1.0.0",
...
"peerDependencies": {
"react": "^16.3.0"
}
}

package-b‘s package.json file looks something like this

{
"name": "@org/package-b",
"version": "1.0.0",
...
"peerDependencies": {
"@org/package-a": "1.0.0",
"react": "^16.3.0"
}
}

And the site‘s package.json file looks like this

{
"name": "my-website",
"version": "1.0.0",
"dependencies": {
"react": "^16.3.0",
"@org/package-a": "1.0.0",
"@org/package-b": "1.0.0"
}
}

There was of course more configuration and dependencies, but for sake of this article I’ll keep it simple and stick with the relevant configuration. This is where I was skeptical, how on earth is yarn going to magically grab the local versions of these libraries and not just go to npm?!

Well this is where the package.json file at the root of the “workspace” comes in. It looks like this:

{
"private": true,
"workspaces": [
"package-a",
"package-b",
"my-website"
]
}

By letting yarn know specific workspaces it can resolve dependencies prioritising local workspaces over remote package repositories.

Once I was all setup it was as simple as running yarn install from the root directory. A bit of magic … et voila. Three days spent battling npm link solved in half an hour using yarn workspaces. Now I understand why people prefer yarn over npm!

--

--