Skip to content

Repository files navigation

vx logo

vx

A fast, correct task runner and build cache for JavaScript monorepos.
One binary. No daemon, no cloud, no account.

npm CI MIT license Documentation

Bar chart: vx against Turborepo and Nx on a 3,270-task graph, cold and fully cached.

Same graph, same commands, same concurrency. Methodology and how to reproduce it.

The runner adds seconds to a cold build where others add minutes. On a 1,090-package graph of 3,270 tasks whose ideal schedule is 3m 38s, vx finishes in 3m 46s (+0:08), Turborepo in 5m 13s (+1:35) and Nx in 34m 44s (+31:06) — 8 ms of overhead per package against 88 ms and 1,712 ms, so the graph can grow and the runner stays in seconds. The cold build burns 35 s of CPU in vx, 73 s in Turborepo and 114 minutes in Nx; a fully cached run replays the graph in 510 ms.

Measured on eleven real Turborepo and Nx repos too: tried on real repos.

Install

npm install -D @vzn/vx     # or: pnpm add -D · yarn add -D · bun add -d

The package ships a prebuilt binary for Linux and macOS, x64 and arm64 (Windows via WSL); no Bun needed. The binary alone, from a release, needs no Node either.

Quick start

In any git repo with a package.json workspace:

npx vx init               # a vx.config.ts per package, from its scripts

Give a task a cache block (what it reads, what it writes):

// packages/app/vx.config.ts
import { defineProject } from '@vzn/vx'

export default defineProject({
  tasks: {
    build: {
      exec: { command: 'tsc -b' },
      dependsOn: ['^build'], // dependencies build first
      cache: {
        inputs: { files: ['src/**', 'tsconfig.json'] },
        outputs: { files: ['dist/**'] },
      },
    },
  },
})
npx vx run build --all    # builds, in dependency order; stores the results
npx vx run build --all    # nothing changed: up-to-date, in milliseconds
rm -rf packages/*/dist
npx vx run build --all    # restored from the cache, not rebuilt

vx run ci --all on examples/basic: cold, three tasks run; again, three up-to-date.

Or copy examples/basic, a two-package starter CI runs on every commit. More: Quickstart · Configure · CLI · every config field · playground (the planner, in your browser).

Already on Turborepo, Nx or moon?

Keep your turbo.json, Nx project graph or .moon/. Two lines run it under vx, with nothing rewritten:

// vx.workspace.ts
import { defineWorkspace } from '@vzn/vx'
import { turbo } from '@vzn/vx-migrate' // or: nx, moon

export default defineWorkspace({ plugins: [turbo()] })

When you want configs of your own, one command writes them:

bunx @vzn/vx-migrate      # vx.config.ts per package, from turbo.json, an Nx graph or .moon/

The written configs derive the same cache keys, so what turbo() cached still hits: examples/turbo runs both steps on every commit.

turboCache() and nxCache() keep the remote cache you have (Turbo's /v8/artifacts API, Vercel's included, or Nx's /v1/cache). What each Turbo and Nx feature is in vx, pinned by a test: parity map · migration guide.

The plugin packages (@vzn/vx-migrate and the rest below) are not on npm yet; only @vzn/vx is.

Why vx

  • A cache you can trust. Configs are TypeScript, evaluated before hashing, so imports and computed values are part of the key. Declared outputs are wiped before every run and every restore: no stale file survives. On a clean tree keys come from git's index, with no file read.
  • Fast where it counts. Milliseconds of overhead per package, no daemon, one git walk per run. Every optimization is recorded with the invariant that keeps it valid (ledger).
  • Sandboxed tasks. Opt in per task: a read the task did not declare fails the build instead of poisoning the cache.
  • Clean exits. Ctrl-C reaps every child. No orphaned dev servers.
  • Plugins at every stage. Remote cache, remote execution, telemetry and new CLI verbs are plugins on documented seams, the way Vite does it. Core ships none and needs none.
  • Free. MIT. No paywall, no cloud, no account.

Tried on real repos

Eleven public monorepos, each run with its own turbo.json or Nx graph and nothing rewritten, against the tool it ships with. Both tools on the same machine and tasks per row, medians; vx first, theirs second. Where the other tool wins, the row says so.

Repo Against Cold build Restore outputs Nothing to do
solidjs/solid Turbo 40.6 s / 45.5 s 66 ms / 127 ms 51 ms / 95 ms
withastro/astro Turbo 48.0 s / 57.9 s 478 ms / 1.43 s 249 ms / 1.38 s
payloadcms/payload Turbo 126.9 s / 127.5 s 3.44 s / 3.46 s 256 ms / 237 ms
medusajs/medusa Turbo 308 s / 315 s 3.86 s / 7.16 s 947 ms / 3.29 s
n8n-io/n8n Turbo 137.2 s / 133.7 s 8.27 s / 12.5 s 842 ms / 1.36 s
calcom/cal.com Turbo 245.7 s / 250.7 s 17.4 s / 19.9 s 14.7 s / 18.5 s
TanStack/query Nx 47.4 s / 55.1 s 656 ms / 1.98 s 174 ms / 1.88 s
strapi/strapi Nx 238.2 s / 245.7 s 1.61 s / 3.94 s 341 ms / 4.02 s
novuhq/novu Nx 291.2 s / 299.4 s 3.10 s / 9.05 s 655 ms / 8.55 s
TanStack/router Nx 199.9 s / 226.7 s 1.00 s / 3.33 s 505 ms / 3.21 s
refinedev/refine Nx 96.2 s / 105.9 s 1.06 s / 1.19 s 303 ms / 1.16 s

Bold marks the other tool winning. Versions, revisions, scopes and harness per repo: benchmarks (astro and refine as re-measured on 2026-09-27, the rest 2026-09-10 and -11).

How it compares

vx Turborepo Nx moon²
Fully cached, 1,090 pkgs¹ 510 ms 760 ms 3.59 s not measured
Config TypeScript, evaluated into the cache key JSON (static) JSON (static) YAML, Pkl and others (static)
Output ownership Strict: wiped before exec and restore Additive (stale files survive) Additive Glob outputs pruned on restore
Clean-tree hashing Zero reads (git index OIDs) git OIDs re-hash / daemon git hash-object
Daemon required for speed No Optional Yes Optional (experimental)
Per-task sandbox Yes: kernel-level, opt-in No No No
Plugin API Yes: every pipeline stage No Yes (TS-tied) WASM toolchains, extensions
OTel CI/CD spans Yes: otel() plugin, no OTel SDK No Paid No
Install Single binary; no Bun, Node optional npm + Node npm + Node Single binary

¹ Warm, nothing to rebuild, the graph charted above. Every gap, and when another tool is the better pick: comparison · vx, Turborepo, Nx, Bazel.

² From moon's own docs (2026-09-28): config, outputs, hashing, daemon, plugins, install; they describe no per-task sandbox and no OpenTelemetry export. Already on moon: moon() runs it unchanged.

Plugins

Declared in vx.workspace.ts. Without one, vx runs and caches on this machine.

Package What it adds
@vzn/vx-migrate Run a Turbo, Nx or moon repo unchanged; write configs; keep a Turbo or Nx cache
@vzn/vx-reapi Remote cache and remote execution over Bazel's REAPI (NativeLink, BuildBuddy)
@vzn/vx-lockfile Key each project on its own lockfile slice: pnpm, bun, npm, yarn
@vzn/vx-otel OpenTelemetry traces, metrics and logs, no SDK
@vzn/vx-github GitHub Actions job summary and Checks API
@vzn/vx-mcp vx mcp: a read-only MCP server for AI coding agents
@vzn/vx-schedule-history Schedule by the critical path learned from past runs

Write your own: plugin guide · architecture.

Status

Pre-alpha, dogfooded: this repo lints, tests and builds itself with vx. Over 4,000 core tests plus the package suites, CI green on every commit. The schema may still change; what 1.0 will promise is in versioning-1.0.md.

Surface Maturity
Core task runner + caching production-ready
Plugin pipeline (13 hooks, commands included) shippable
vx init / @vzn/vx-migrate (scripts; Turbo, Nx) shippable
First-party plugins (table above) shippable

Contributing

git clone https://github.com/vznjs/vx && cd vx
bun install
bun packages/vx/src/bin.ts run ci --all     # lint, test, docs build: every package

Needs Bun ≥ 1.4. Every dev task is a vx task; there are no package.json scripts. Start with CONTRIBUTING.md and STATUS.md, the living handoff.

License

MIT

About

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages