← All writing
Craft · · 5 min

Yarn, because npm

The lockfile was the actual point. It took one bad March morning to work that out.

Tooling JavaScript

Facebook put out a package manager two weeks ago. I’ve moved three projects to it since, which took about 40 minutes in total, and everything is faster and more predictable and I’m pleased.

I also wrote almost exactly this post in January last year about moving a build from Grunt to Gulp, in which I saved 11 seconds and spent a week. So before I get enthusiastic I’d like to check whether I’ve learned anything, because the shape is identical and the shape is what worries me.

Sure, this time it’s different

Which is what I said last time, so let me be specific about why I think it’s true here.

Grunt to Gulp was a rewrite. Different config format, different mental model, every plugin replaced with a different plugin, and a week of finding out that the one thing I needed didn’t have an equivalent yet.

Yarn isn’t that. It reads the same package.json, installs from the same registry, and produces the same node_modules. The migration is one command, plus committing a file.

terminal
yarn                          # reads package.json, writes yarn.lock
git add yarn.lock             # this is the entire point
yarn add lodash               # instead of npm install --save
yarn add --dev gulp
Shell

If it turns out to be a mistake, npm install still works and nothing has been thrown away. That’s not nothing. A migration you can walk back out of is a completely different risk from one you can’t, and I don’t think I gave that any weight at all in 2015.

Lock it down

Installs are much faster, and I don’t want to be sniffy about that, because I do it several times a day and the parallel fetching and the local cache are real.

But the thing that actually changes my life is the lockfile.

The problem it solves is one I’d been quietly blaming myself for. A dependency of ^1.2.3 means “anything up to 2.0,” which means my install last Tuesday, your install this morning, and the build server’s install tonight can all resolve to different code, and none of us did anything. Then something breaks in one place and not another, and you spend an afternoon on it, and the afternoon feels like a personal failing rather than a property of the system.

"Works on my machine" has been a package manager problem this entire time, and I'd been taking it personally.

There was npm shrinkwrap for this and I never used it reliably, partly because it was opt-in and partly because it had a reputation for being fussy, which meant nobody on any project I worked on used it either. A lockfile that’s generated automatically and just sits in the repo is the same idea with the friction taken out, and the friction was doing all the damage.

And then there was March. A package got unpublished, a lot of builds stopped working that morning, and the general reaction was to be amused for a day and then carry on exactly as before. That was the moment to take reproducibility seriously and most of us didn’t, because there was nothing convenient to do about it.

Same, not safe

Worth being clear, since the marketing gets vague here.

The lockfile guarantees you get the same thing, not that you get anything. It’s the same registry as before, so if a package disappears or the registry has a bad day, a lockfile doesn’t conjure the tarball. There’s an offline cache that helps with this and I’ve not tested it properly yet.

It also does nothing whatsoever about the actual problem, which is that a five-page site has a node_modules folder with 800 packages in it that nobody has read. Installing that faster and more deterministically is an improvement to a situation I should probably be more alarmed by than I am.

Rationalizing after the fact

Now the less flattering version.

I’ve now applied a test to this decision: did the migration cost less than it saved. It comfortably did, 40 minutes against a recurring problem that eats afternoons, and by that measure this was a good call and Grunt to Gulp wasn’t.

Except I applied that test after I’d migrated all three projects. I read the announcement, I was interested, and I did it that evening, and the evaluation is something I constructed afterward to explain a thing I was always going to do.

Which is a less flattering account of how I pick tools than the one I’d give in an interview. I’d like to think I’m assessing things. I think what I’m mostly doing is being available.

The verdict, for now

Use it. It’s good, it’s cheap to try, it’s cheap to leave, and the lockfile is the first new idea in this corner of my toolchain in a couple of years.

I’ll set a reminder to reread this in 18 months, when the noun has changed again and I’m writing the third verse.

Read similar posts
9 min

Write it down once

Most of what makes Claude Code work for me isn't prompting, it's a few files I wrote once: a plan-mode habit, a /simplify pass welded onto /commit, a hook that won't let me hand-write a commit, and a CLAUDE.md that's mostly scar tissue.

16 min

It said it was working

Porting a Claude Code safety tool from Linux to macOS turned into six weeks of discovering that nearly every failure mode on the new platform was silent, and that the tool's own adversarial test suite was the only reason I could see any of them.