Skip to content

perf(pm): skip the npm --version spawn in npm projects - #3259

Merged
fengmk2 merged 4 commits into
voidzero-dev:mainfrom
magurotuna:perf/npm-version-from-package-json-v2
Oct 29, 2026
Merged

fengmk2 merged 4 commits into
voidzero-dev:mainfrom
magurotuna:perf/npm-version-from-package-json-v2

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Summary

This PR saves about 110 ms on every vp run and every package-manager command (vp install, vp add, vp list, vp outdated, and others) in npm projects by making vp read the npm version from npm's package.json instead of running npm --version.

Benchmark

Command (npm project) Before After
vp run noop 225 ms 117 ms (−48%)
vp list 463 ms 346 ms (−25%)

noop is a script that runs node -e 0. Mean of 30 runs on:

  • CPU: AMD EPYC (Rome), 8 vCPUs (KVM virtual machine)
  • Memory: 16 GiB
  • OS: Debian 13 (Linux 6.12)
  • Node.js 24.21.0, npm 11.19.0

Approach

vp needs the npm version to build the right command line and the npm_config_user_agent for scripts. It ran npm --version for this, which starts Node.js and loads npm only to print the version field of npm's own package.json. Reading that file directly skips the process.

If the npm on PATH does not resolve into npm's package (for example, a version-manager shim, Corepack, or npm.cmd on Windows), vp still runs npm --version.

This PR only affects npm cases. pnpm and yarn leave room for a similar speedup: in system-first mode, vp runs pnpm --version or yarn --version, and the JavaScript builds (pnpm 11 and earlier, and yarn) answer slowly. However, pnpm and yarn can switch to the version that the project selects, so reading their package.json would not be correct. This is left for a follow-up.

In a project that uses npm, `vp run`, `vp list`, `vp outdated`,
`vp install`, and the other package-manager commands first ran
`npm --version` to learn the version of the npm on PATH. That starts Node.js
one more time for every command. Two paths ran it: the default resolution
(`resolve_npm_from_path`, which `vp run` also uses) and the system-first
shim mode (`system_package_manager` in the global CLI).

`npm --version` prints the `version` field of npm's own package.json. The npm
on PATH normally links into its package (`bin/npm` ->
`lib/node_modules/npm/bin/npm-cli.js`), so both paths read that manifest
now. If the nearest package.json within four parents does not belong to npm
(for example a version-manager shim, Corepack, or Windows `npm.cmd`), they
run `npm --version` as before.

The manifest belongs to the npm on PATH, not to the npm that ships with
Node.js, so an npm in a separate global prefix keeps its own version. npm does
not switch versions per project, so the value matches `npm --version`. pnpm,
yarn, and bun do not change: `vp` resolves their version itself and runs
`--version` only in system-first mode, and pnpm and yarn can switch to the
version that the project selects before they answer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@magurotuna
magurotuna force-pushed the perf/npm-version-from-package-json-v2 branch from 68dbcc6 to fbc1817 Compare October 28, 2026 11:24
@fengmk2

fengmk2 commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

CI flaky issue has been fixed in main branch, you can merge commit from main. 😭

@liangmiQwQ

Copy link
Copy Markdown
Collaborator

This was a design left by me actually, I'll share some context:

npm --version call is designed to only happen for system-first mode. Because Vite+ doesn't take control of package manager, we cannot assume that the user's own package manager will be the declared version in package.json, the output npm --version then becomes the source of truth.

I originally designed a prove mechanism to throw error when the actual npm version is not the same as the declared version. However, I eventually dropped the mechanism because I think the actual version is known at that time and users would prefer for the command to execute successfully right away. (This part is open to discussion)

Also, the current vp install also calls npm --version even when in managed mode, it should be my mistakes. Fixing this is quite fine to me.

@magurotuna

Copy link
Copy Markdown
Member Author

@liangmiQwQ
Thanks for the context!

I don't think this PR changes which npm version vp uses in system-first mode. With this PR, vp reads the version field from npm's own package.json, not the user project's package.json. More concretely, it finds npm on PATH, resolves the symlink, and walks up the parent directories to npm's package.json. If it does not find npm's package.json there (for example, with a version-manager shim, Corepack, or npm.cmd on Windows), it falls back to npm --version.
In fact, the version field in npm's package.json is what npm --version prints as we can see in the npm implementation here:
https://github.com/npm/cli/blob/bfacd33ccbcd908480610703b60455d2da5b57a9/lib/npm.js#L12

So in short, this PR is to imitate what npm --version does, but without spawning a Node.js process, which takes about 110 ms per command.

@magurotuna
magurotuna marked this pull request as ready for review October 29, 2026 03:18
@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests test: sfw labels Oct 29, 2026
@fengmk2

fengmk2 commented Oct 29, 2026

Copy link
Copy Markdown
Member

@magurotuna Let me take care of the flaky ci 😢

@fengmk2
fengmk2 merged commit 5d7a0f6 into voidzero-dev:main Oct 29, 2026
141 checks passed
@liangmiQwQ

Copy link
Copy Markdown
Collaborator

@liangmiQwQ

Thanks for the context!

I don't think this PR changes which npm version vp uses in system-first mode. With this PR, vp reads the version field from npm's own package.json, not the user project's package.json. More concretely, it finds npm on PATH, resolves the symlink, and walks up the parent directories to npm's package.json. If it does not find npm's package.json there (for example, with a version-manager shim, Corepack, or npm.cmd on Windows), it falls back to npm --version.

In fact, the version field in npm's package.json is what npm --version prints as we can see in the npm implementation here:

https://github.com/npm/cli/blob/bfacd33ccbcd908480610703b60455d2da5b57a9/lib/npm.js#L12

So in short, this PR is to imitate what npm --version does, but without spawning a Node.js process, which takes about 110 ms per command.

Oh! I previously thought you are reading projects' package.json, sorry for misunderstanding! ♥️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants