Skip to content

perf(pacquet): no-op short-circuit when node_modules and lockfile are already consistent #11899

Description

@zkochan

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.jsonlockfile+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:

  1. 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.

  2. 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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions