The fastest task runner for JS monorepos.
Measured against Turborepo and Nx, each in its own native config.
| 1,090 packages, 3,270 tasks | vx | Turborepo | Nx | Vite Task |
|---|---|---|---|---|
| Cold build: time the runner adds | 2 s | 1 min 21 s (vx 34× faster) | 11 s (vx 4.7× faster) | 1 min 11 s (vx 30× faster) |
| Cold build: CPU burned | 17 s | 21 s (vx 1.2× faster) | 52 s (vx 3× faster) | 12 s (vx 1.4× slower) |
| Fully cached run (restored) | 650 ms | 997 ms (vx 1.5× faster) | 6.25 s (vx 9.6× faster) | 2.64 s (vx 4× faster) |
| Fully cached run (up-to-date) | 393 ms | 463 ms (vx 1.1× faster) | 6.45 s (vx 16× faster) | 2.49 s (vx 6.3× faster) |
vx N× faster: that tool takes N times as long as vx (theirs ÷ vx); N× slower: vx takes N times as long (vx ÷ theirs).
Time added is the wall time over the tasks' own ideal schedule (3 min 38 s). Same graph, commands and concurrency: how it is measured.
A task runner and build cache for JavaScript monorepos. Each task is one shell command with declared inputs and outputs; vx runs them in dependency order and replays results from a content-addressed cache.
npm install -D @vzn/vx # or: pnpm add -D -w · yarn add -D (-W on Yarn 1) · bun add -dA prebuilt binary for Linux (glibc, not Alpine's musl) and macOS, x64 and arm64; no Bun needed. The release binary needs no Node either. Windows: use WSL.
In a git repo with a package.json workspace:
npx vx init # a vx.config.ts per package, from its scriptsGive a task a cache block, what it reads and writes:
// packages/app/vx.config.ts
import { defineProject } from '@vzn/vx/config'
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 rebuiltStarter: examples/basic, run by CI on every commit.
More: Quickstart ·
Configure ·
CLI · every config field ·
playground (the planner, in your browser).
bunx @vzn/vx-migrate writes a vx.config.ts per package from
turbo.json or the Nx graph; you keep those and delete the old config.
Beside turbo.json or nx.json, vx init writes only a
vx.workspace.ts that maps the old config until then: a temporary
start, not a way to run the repo. The numbers above are native vx
config. @vzn/vx-migrate's first npm publish is pending.
Migration guide.
- Nothing boots per task: no daemon, no Node process; vx spawns your command.
- No source-file reads to hash: on a clean tree, keys come from git's index; each project's
package.jsonand the lockfile are still read. - One pass: one git walk per run; each stat and lookup happens once.
- A cache you can trust: TypeScript configs are evaluated before hashing, so imports and computed values are in the key. Outputs are wiped before every run and restore: no stale file survives.
- Sandboxed tasks: opt in per task; a workspace file the task did not declare is out of its reach, so it cannot poison the cache.
- Clean exits: Ctrl-C reaps each task's process group.
- Plugins at every stage (14 hooks,
commandsincluded): remote cache, remote execution, telemetry, CLI verbs. Core ships none and needs none. - Free: MIT. No paywall, no cloud, no account.
| vx | Turborepo | Nx | |
|---|---|---|---|
| Config | TypeScript, evaluated into the cache key | JSON (static) | JSON (static) |
| Output ownership | Strict: wiped before exec and restore | Additive (stale files survive) | Additive |
| Clean-tree hashing | No source reads (git index OIDs) | git OIDs | re-hash / daemon |
| Daemon | None | None for turbo run |
On by default |
| Per-task sandbox | Yes: kernel-level, opt-in | No | No |
| Plugin API | Yes: every pipeline stage | No | Yes (TS-tied) |
| Install | Single binary; no Bun, Node optional | npm + Node | npm + Node |
Every gap, and when another tool is the better pick: comparison · vx, Turborepo, Nx, Bazel.
Declared in vx.workspace.ts; without any, vx runs and caches locally.
| Package | What it adds |
|---|---|
@vzn/vx-migrate |
Migrate from Turbo or Nx: write native configs, keep its remote 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-ci |
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.
Pre-alpha, dogfooded: this repo lints, tests and builds itself with vx. The schema may still change; 1.0's promise: versioning-1.0.md.
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 packageNeeds Bun ≥ 1.4. Every dev task is a vx task; no package.json
scripts. Start with CONTRIBUTING.md and
STATUS.md.