What vx is, and what it refuses to be
vx is a task runner and a content-addressed cache for JavaScript monorepos, and nothing else. This post is the shape of the thing: the five stages, the seams, and the list of features that will never be inside.
vx is a task runner and a content-addressed cache for JavaScript monorepos, and nothing else. This post is the shape of the thing: the five stages, the seams, and the list of features that will never be inside.
A fully cached run of 3,270 tasks finishes in about half a second with no daemon. That number is the sum of five structural decisions, each of which is also a correctness win.
Turborepo is fast and stops at the edge of a laptop. Nx scales and is a product with a runner attached. Between a tool that will not grow and a platform that will not get out of the way, the thing a large JavaScript monorepo actually needs did not exist. That is why vx does.
Every design decision in vx is settled by three drivers in a fixed order, and nine principles that follow from them. This is the list, with what each one has already cost and what it has bought.
Hashing inputs is the cost every cache pays. vx pays it once, in git, and reads the blob ids back. Here is how the key is derived, part by part, and why a commit never flips it.
A vx.config.ts can import a preset, compute a command, read a constant from another file. The cache key sees the evaluated object, so all of that participates in the key. Static JSON tools cannot see it at all.
Turborepo and Nx restore outputs on top of whatever is already there. vx wipes the declared outputs first, on a miss and on a hit, so the tree after either is the cached snapshot and nothing else. Stale files cannot survive.
When a library changes, its dependents must re-key. There are two ways to carry that change downstream, and vx tried both. It settled on the one that lets every key in the graph be known before anything runs.
A daemon answers 'what changed' quickly by keeping a second copy of the truth. vx keeps no copy. Every run pays its own discovery and still wins the warm benchmarks, because the discovery was made cheap instead of being hidden.
On a 3,270-task graph, computing scheduling priority with set unions took 8.5 seconds. Packed bitsets with popcount take single-digit milliseconds. This post is the scheduler: what it computes, how it picks the next task, and the two-tier trick that keeps restores off the critical path.