Repository navigation
Conversation
`npm exec` / `npx` required `@npmcli/arborist` and `pacote` at the top of libnpmexec, so running a bin that is already in `node_modules/.bin` loaded about 420 modules (fetchers, make-fetch-happen, sigstore, tuf-js, cacache, tar, ...) that this path never uses. - require arborist and pacote on first use - read the local package.json for `hasPkgBin` with `@npmcli/package-json` directly, which is what pacote's DirFetcher does for a directory spec - with `--call` and no `--package`, run the command without reading the local tree, whose result was never used `npx <local bin>` and `npx -c <cmd>` now load 140 modules instead of 560 before spawning the command. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related: #10084 (Windows shims start node once per call) and #10097 (does
not list network interfaces at start-up). Each removes a different part of npm's start-up cost;
they touch different files and the savings add up.
Summary
npx <bin>/npm exec -- <bin>with<bin>already innode_modules/.bin(npx tsc,npx eslint,npx vitest run…) loads 560 modules before it spawns the bin. About 420 ofthem are
@npmcli/arboristandpacoteand what they pull in: all of pacote's fetchers,make-fetch-happen,@npmcli/agent,sigstore/tuf-js,cacache,tar… That path uses noneof them. libnpmexec requires both at the top of
lib/index.js, and the only call on this path,hasPkgBin(), goes throughpacote.manifest(dir)just to read the project'spackage.json.This PR, in
workspaces/libnpmexec/lib/index.js:@npmcli/arboristandpacoteon first use, and./strict-allow-scripts-preflight.js(which reaches into Arborist) where it is called. This isthe approach of fix: lazy loading of arborist and pacote #6225 and fix(perf): lazy loading optimizations #7388 for
lib/commands/*, applied to libnpmexec, which they did nottouch.
hasPkgBin()callsPackageJson.normalize(dir)directly. That is what pacote'sDirFetcher.manifest()does for a directory spec; pacote only adds_resolved/_integrity/_from, whichhasPkgBindoes not read. Because pacote is no longer involved, theallowDirectoryoption it used to be passed has nothing to gate.npm exec --call/npx -cwithout--packageruns the command beforeArborist.loadActual(). With no packages, the loaded tree was never read.With the patch,
npx <local bin>andnpx -c <cmd>load 140 modules. Arborist and pacote loadexactly as before whenever a package has to be looked up or installed.
Numbers
npm packoflatest(b317f16), against the same tarball with this commit;node npx-cli.js …directly (no shim), in a TypeScript project.difference with its 95% CI.
npx --no-install tsc --version, Dev Drivenpx --no-install -c "tsc --version", Dev Drivenpx --no-install -p typescript tsc --version(needs Arborist: control), Dev Drivenpm --version(control), Dev Drivetsc --versionitself takes 73 ms andnode -e 045 ms on this machine, so npm's own share ofnpx tsc --versiongoes from about 470 to about 200 ms on the Dev Drive. A second run, in a cleanuser environment and with the machine under more load, saved more: 318 ms (303–336) on the Dev
Drive and 518 ms (505–552) on the system drive.
The module and
fscall counts, before and after, are the same on Windows and on Linux, where thechange was also tested.
Behaviour
--package,directory specs, the npx cache, global bins).
hasPkgBin: reads the same normalizedpackage.jsonas before.node_modulesused to makenpx -c '<cmd>'fail insideloadActual();now it runs the command, as
npm rundoes.Tests
workspaces/libnpmexec/test/local.js› "call without packages does not load the localtree". It counts
loadActual()calls through the existing Arborist mock pattern, and failswithout change 3.
node . run test -w workspaces/libnpmexec: passes, 100% coverage;node . run lint: clean;node . run test: passes excepttest/bin/windows-shims.js,test/lib/commands/publish.jsand
test/lib/commands/stage/index.js, which fail the same way on unmodifiedlateston thetest machine (a PowerShell profile message, and scripts that call
touch).Composition
workspaces/libnpmexec/lib/index.js: the top-of-filerequires and thepacote.manifest/new Arboristcall sites. The rebase is mechanical whichever lands first (Arborist→getArborist(),pacote→getPacote()).References
npm execoverhead, "the overhead of loading every npm dependencies".