Say which projects may depend on which
Say which projects may depend on which
Tag your projects by layer and say which may depend on which. vx lock refuses a dependency that breaks a rule, and runs pay nothing.
One command writes native vx config from the project graph your installed Nx resolves, plugin-inferred targets included.
One command writes native vx config from the graph your installed Nx resolves. Targets that Nx plugins infer come along.
$ bunx @vzn/vx-migrate from nx graph ran bun add -d @vzn/vx @vzn/vx-lockfile @vzn/vx-history tasks 2 clean files libs/ui/vx.config.ts apps/web/vx.config.ts .vx/workspace.ts .vx/.gitignore lock 2 project configs → .vx/projects.lock.json
next vx run build --all
$ vx run build --all ⏺︎ 9ms success miss ui#build ⏺︎ 7ms success miss web#build$ rm -rf apps/web/dist && vx run build --all cache local 1✓ 1↓Two Nx projects, Nx 24.0.1 · vx-migrate 69617eb · 2026-10-23
flowchart LR N[nx.json + Nx plugins] --> G[nx graph: resolved targets] G --> M[bunx @vzn/vx-migrate] M --> C[vx.config.ts per project] M --> W[.vx/workspace.ts]
import type { ProjectConfig } from '@vzn/vx/config'
export default { tasks: { build: { exec: { command: 'bun build src/index.ts --outdir dist', }, dependsOn: ['^build'], inputs: { files: ['src/**/*'], }, outputs: { files: ['dist'], }, cache: true, }, },} satisfies ProjectConfigtargetDefaults and namedInputs are already applied in the graph, so
each task gets its own values. In vx a shared list is an import.
To check first, bunx @vzn/vx init --keep writes only this:
import type { WorkspaceConfig } from '@vzn/vx/config'import { nx } from '@vzn/vx-migrate'import { npm } from '@vzn/vx-lockfile'import { vxHistory } from '@vzn/vx-history'
export default { plugins: [ nx(), npm(), vxHistory(), ],} satisfies WorkspaceConfig{ "build": { "executor": "@nx/js:tsc", "options": { "main": "src/index.ts", "tsConfig": "tsconfig.lib.json" } } }Each becomes an nx-exec line that runs the executor through Nx’s
public runExecutor, so nothing breaks on day one.
An executor target is written as its nx-exec line, and you rewrite it
as the real command when you want Nx gone:
build: { exec: { command: 'tsc -b tsconfig.lib.json' }, inputs: { files: ['src/**', 'tsconfig.lib.json'] }, outputs: { files: ['dist/**', '*.tsbuildinfo'] }, cache: true,}These server executors come through as persistent tasks, and so does
any target Nx marks continuous:
@nx/vite:dev-server, @nx/vite:preview-server@nx/webpack:dev-server, @nx/next:server@nx/storybook:storybook, @nx/web:file-server@nx/js:node, @nx/js:verdaccio@angular-devkit/build-angular:dev-server| Nx | vx |
|---|---|
a project’s targets |
tasks |
dependsOn (^build, …) |
dependsOn, same syntax |
inputs / namedInputs (resolved) |
inputs.files |
{workspaceRoot}/file |
inputs.workspaceFiles |
outputs |
outputs.files |
nx affected |
vx run … --affected[=<base>] |
nx run-many --projects |
vx run … --filter |
parallelism: false |
a TODO naming --concurrency 1 |
nx watch |
vx watch |
targetDefaults |
already applied in the graph; share them as a preset you import |
| 9,603 tasks, native config | vx | Nx |
|---|---|---|
| Nothing changed | 559 ms | 12.07 s |
| Cold build: CPU the runner burns | 42.19 s | 5 min 31 s |
Synthetic monorepo, 1,601 projects · concurrency 10 · benchmarks
Measured: vx 0.0.0 @ ee4c63a57 on 2026-11-12, Turborepo 2.11.10 on 2026-11-12, Nx 24.0.2 on 2026-11-12, Vite Task 1.4.0 on 2026-11-12; linux x64, 4 cores (AMD EPYC 7763 64-Core Processor), 16 GB.
@vzn/vx-dte (alpha) on your own CI
(how), or @vzn/vx-reapi on a
Bazel pool.nxCache() speaks the self-hosted /v1/cache wire.vx run build --all --dry=json or vx show.Generators have no vx replacement. Guide: Migrate from Nx. More at vznjs.github.io/vx.