An afternoon in the sandbox with vlt
vlt (pronounced "volt") is a new JavaScript package manager from Isaac Schlueter — the creator of npm — and much of the original npm team, currently at 1.0.0-rc.32. I spent an afternoon putting it in a sandbox next to pnpm, and here is the whole verdict up front: there is no reason to switch today, and one very good reason to install it anyway. The layout, the disk story, and the security posture are pnpm's ideas arriving in a second body. The genuinely new part is vlt query, a CSS selector language over your dependency graph — and it works on a pnpm-installed project, without migrating anything.
Everything below comes from that one afternoon: same machine, same network, one run per measurement. Read it as a field report, not a benchmark paper.
Install and execute are two different verbs
Drop express and vitest into an empty project, run vlt install, and five seconds later it tells you:
"message": "2 packages that will need to be built,
run \"vlt build\" to complete the install."Packages that want to run native builds — esbuild, fsevents — get unpacked but not executed. Executing them is a separate command, vlt build, that you run when you decide to trust them. pnpm has moved the same direction with approve-builds, and that convergence is the real story: with most supply-chain attacks entering through postinstall scripts, separating "put files on disk" from "run code on my machine" has stopped being a bold idea and become table stakes. vlt just promotes it to a first-class verb.
The node_modules layout is pnpm, with one new idea
Look inside and the top level holds only your direct dependencies; everything real lives behind symlinks:
node_modules/express
-> .vlt/~npm~express@4.22.2/node_modules/expressThat is pnpm's .pnpm design — same phantom-dependency protection, and on macOS the same copy-on-write reflinks into a central cache, so disk usage came out identical in my tests (95 MB for the same 186-package tree).
The one new idea is sitting in the directory name: ~npm~express@4.22.2. The registry is part of the package's address. vlt is built by a company whose other product is a serverless registry (vsr), and the layout assumes a multi-registry future — npm, JSR, your own — down to the filesystem.
The good part: your dependency graph, queryable in CSS
Speed is not vlt's pitch. This is:
vlt query '#send' # where does this transitive dep come from?
vlt query ':root > :dev' # direct dev dependencies
vlt query ':not([license=MIT]):not(:root)' # everything that isn't MIT
vlt query ':outdated(major)' # a major behind
vlt query ':severity(high)' # known vulnerabilities (Socket.dev data)My favorite selector of the afternoon:
vlt query ':attr(scripts, [postinstall])'One line answers "who wants to execute code on my machine?" — in my sandbox, only vite → esbuild raised its hand.
Questions that pnpm splits across pnpm why, pnpm audit, and pnpm licenses are one language here. The same selectors target script runs (vlt run --scope=':workspace' test, pnpm's --filter) and render dependency diagrams (--view=mermaid). One thesis runs through the whole tool: a dependency graph is data, and data should be queryable.
It works on your pnpm project, today
Here is the part you can use without migrating: point vlt query at a project pnpm installed, with no vlt lockfile anywhere, and it reads the existing node_modules and builds the graph. License audits and postinstall hunts on your current repos, no commitment.
One honest caveat: my "read-only" query quietly wrote a cache file, node_modules/.vlt-lock.json. It is outside git and harmless, but know it happens before you point the tool at someone else's checkout.
Workspaces, and a silent miss
Monorepos are configured in vlt.json, and the workspace:* protocol resolves to clean relative symlinks. It works.
Getting there taught me something about the RC, though. I first wrote the config key as "workspace" — singular — and vlt reported success with an empty dependency graph. No error, no warning, no "unknown key." The correct key is "workspaces". A tool this young should be loudest exactly there.
The numbers, honestly
React + Vite + ESLint, 186 packages. Cold means no cache and no lockfile; warm means node_modules deleted and reinstalled.
| cold | warm | |
|---|---|---|
| vlt | 3.8s | 1.07s |
| pnpm | 4.1s | 0.53s |
| npm | 8.7s | 0.95s |
The official "73% faster than npm" claim checks out for cold installs, and against pnpm, cold is already a tie. But warm installs — the path you walk twenty times a day — pnpm is still cleanly twice as fast, and it saturates every core while vlt idles below one. On a smaller dependency set the cold ranking flipped back to pnpm, so treat cold as a coin toss and warm as pnpm's win.
RC rough edges, for the record
vlt -aprints its help as a raw JSON-escaped string,\nand allvlx cowsay(the npx equivalent) hangs silently behind a pipe, waiting on a confirmation prompt you cannot see — pass--yes- every install helpfully drops a
.gitignoreand.npmignoreinto your project, whether you asked or not
None of these are structural. The parts that matter held: vitest and esbuild both ran correctly on top of the symlink layout, first try.
Where this leaves you
Keep pnpm. It is faster where it counts and it has years of edges sanded off.
But vlt is not trying to be pnpm again. The layout war is over and everyone converged on the same answer; vlt's bet is one layer up — that the graph should be queryable, and that registries come in plural. So take the piece that is ready: install vlt next to pnpm, keep your lockfiles exactly as they are, and run vlt query ':attr(scripts, [postinstall])' on the repo you maintain. If the answer surprises you, the tool has already paid for itself.
I'll sit back down with it when 1.0 lands and the warm installs catch up.