Summary
When node_modules/ and pnpm-lock.yaml are mutually consistent and the manifest hasn't changed, pnpm install exits in ~500ms. pacquet install --frozen-lockfile re-runs BuildModules and re-links the entire tree even when nothing has changed.
This is the single biggest gap between pacquet and pnpm in the vlt.sh benchmarks: every fixture in the lockfile+node_modules variation is 2×–24× slower on pacquet, dragging the average down across all variations.
Benchmark evidence
From benchmarks.vlt.sh/latest/chart-data.json — lockfile+node_modules variation, pacquet 0.2.8 vs pnpm 11.2.2:
| Fixture |
pnpm |
pacquet |
ratio |
| next |
0.51s |
1.66s |
3.2× slower |
| astro |
0.51s |
6.29s |
12.4× slower |
| svelte |
0.54s |
1.03s |
1.9× slower |
| vue |
0.51s |
4.39s |
8.7× slower |
| large |
0.58s |
9.70s |
16.8× slower |
| babylon |
0.64s |
15.01s |
23.5× slower |
vlt's lockfile+node_modules.sh prepare step wipes the metadata cache but preserves pnpm-lock.yaml and node_modules/, so the install is conceptually a no-op.
Current pacquet behavior
After the dispatch at pacquet/crates/package-manager/src/install.rs#L503-L544, the frozen-lockfile branch always runs InstallFrozenLockfile.run() → BuildModules. There is no early-exit gate that checks whether node_modules/.modules.yaml is already consistent with the lockfile and manifest.
The Rust Roadmap (#11633) Stage 1 Tier 4 lists .modules.yaml validation, but in the opposite direction — forcing a re-install when layout settings change. The benign-no-op short-circuit isn't tracked.
Proposed approach
Port pnpm's two-part gate:
-
Manifest ↔ lockfile freshness — already implemented as check_lockfile_freshness in pacquet/crates/package-manager. The frozen-lockfile dispatcher already calls this for fatal-on-stale behavior; lift its Ok(()) result into a fast-path eligibility signal.
-
Lockfile ↔ node_modules freshness — port pnpm's validateModules and the allProjectsAreUpToDate body check at installing/deps-installer/src/install/index.ts#L913-L921. Read node_modules/.modules.yaml and verify that the recorded nodeLinker / hoistPattern / virtual-store-dir / enableGlobalVirtualStore / etc. match the current config, and that the recorded snapshots match the current pnpm-lock.yaml.
When both gates pass, skip BuildModules entirely and exit with the same log output pnpm emits on the up-to-date path (Lockfile is up to date, resolution step is skipped).
Expected impact
Closes the entire lockfile+node_modules variation column on every fixture. Likely also helps cache+lockfile+node_modules on large and babylon (currently 3-4× slower).
Related
Written by an agent (Claude Code, claude-opus-4-7).
Summary
When
node_modules/andpnpm-lock.yamlare mutually consistent and the manifest hasn't changed,pnpm installexits in ~500ms.pacquet install --frozen-lockfilere-runsBuildModulesand re-links the entire tree even when nothing has changed.This is the single biggest gap between pacquet and pnpm in the vlt.sh benchmarks: every fixture in the
lockfile+node_modulesvariation is 2×–24× slower on pacquet, dragging the average down across all variations.Benchmark evidence
From
benchmarks.vlt.sh/latest/chart-data.json—lockfile+node_modulesvariation, pacquet 0.2.8 vs pnpm 11.2.2:vlt's
lockfile+node_modules.shprepare step wipes the metadata cache but preservespnpm-lock.yamlandnode_modules/, so the install is conceptually a no-op.Current pacquet behavior
After the dispatch at
pacquet/crates/package-manager/src/install.rs#L503-L544, the frozen-lockfile branch always runsInstallFrozenLockfile.run()→BuildModules. There is no early-exit gate that checks whethernode_modules/.modules.yamlis already consistent with the lockfile and manifest.The Rust Roadmap (#11633) Stage 1 Tier 4 lists
.modules.yamlvalidation, but in the opposite direction — forcing a re-install when layout settings change. The benign-no-op short-circuit isn't tracked.Proposed approach
Port pnpm's two-part gate:
Manifest ↔ lockfile freshness — already implemented as
check_lockfile_freshnessinpacquet/crates/package-manager. The frozen-lockfile dispatcher already calls this for fatal-on-stale behavior; lift itsOk(())result into a fast-path eligibility signal.Lockfile ↔ node_modules freshness — port pnpm's
validateModulesand theallProjectsAreUpToDatebody check atinstalling/deps-installer/src/install/index.ts#L913-L921. Readnode_modules/.modules.yamland verify that the recordednodeLinker/hoistPattern/ virtual-store-dir /enableGlobalVirtualStore/ etc. match the current config, and that the recorded snapshots match the currentpnpm-lock.yaml.When both gates pass, skip
BuildModulesentirely and exit with the same log output pnpm emits on the up-to-date path (Lockfile is up to date, resolution step is skipped).Expected impact
Closes the entire
lockfile+node_modulesvariation column on every fixture. Likely also helpscache+lockfile+node_modulesonlargeandbabylon(currently 3-4× slower).Related
.modules.yamlvalidation (the opposite direction)Written by an agent (Claude Code, claude-opus-4-7).