ADR 004: pnpm as Package Manager

Status: Accepted Date: 2025-03-15 Last Updated: 2026-02-26

Context

We need a package manager for both the monorepo and the MFE polyrepos. It must support workspaces, be fast, and handle the complexities of Module Federation shared dependencies correctly.

Decision

Use pnpm 10.30.3 across all repositories. Pin via Corepack ("packageManager": "pnpm@10.30.3" in root package.json).

pnpm 10 Migration Notes

pnpm 10 introduces several breaking changes from pnpm 9.x that must be addressed:

Lifecycle Scripts Disabled by Default

In pnpm 10, pre/post lifecycle scripts (e.g., preinstall, postinstall, prepare) are no longer run by default. To restore the previous behavior, either:

  • Add enable-pre-post-scripts=true to .npmrc in each repository root, or
  • Use the onlyBuiltDependencies allowlist in package.json to explicitly permit specific packages to run install scripts (recommended for security).

Hoisting Changes

pnpm 10 changes the default hoisting behavior. The public-hoist-pattern defaults have been narrowed. For Module Federation shared dependencies, ensure .npmrc explicitly sets:

public-hoist-pattern[]=*types*
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=react
public-hoist-pattern[]=react-dom

Other pnpm 10 Migration Considerations

  • The resolution-mode default changed to highest in pnpm 10. If you relied on lowest-direct, set it explicitly in .npmrc.
  • pnpm-lock.yaml format has been updated (lockfile v9). The first pnpm install after upgrading will migrate the lockfile automatically. Commit the updated lockfile.
  • The --filter flag behavior has been tightened; verify workspace filter scripts still work after upgrading.
  • Node.js 18 is the minimum required version for pnpm 10.
pnpm 10.30.3 Decision Card pnpm 10.30.3 Positive Content-addressable store Strict dep isolation workspace:* protocol Fast installs Negative Strict node_modules issues .npmrc config needed Lifecycle scripts disabled

Consequences

Positive

  • Content-addressable store: packages stored once on disk, hard-linked into node_modules (saves significant disk space)
  • Strict dependency isolation by default (can't accidentally use undeclared dependencies)
  • Native workspace support with workspace:* protocol
  • Faster installations than npm/yarn in most benchmarks
  • pnpm-lock.yaml is deterministic and easier to review than package-lock.json
  • Built-in support for .npmrc overrides per workspace
  • Corepack-compatible for version pinning (pnpm 10.30.3)

Negative

  • Strict node_modules structure can cause issues with tools that expect flat hoisting (Module Federation shared deps, some legacy packages)
  • Requires .npmrc configuration for public-hoist-pattern to work with MF shared dependencies
  • Smaller community than npm (though growing rapidly)
  • Some CI environments don't have pnpm pre-installed (need corepack enable or pnpm/action-setup)
  • pnpm 10 disables lifecycle scripts by default, requiring explicit .npmrc configuration or onlyBuiltDependencies allowlist

Alternatives Considered

  • npm: Ubiquitous but slow, flat node_modules causes phantom dependencies, workspace support is less mature. No content-addressable store.
  • yarn (v3/v4 with PnP): Plug'n'Play is innovative but causes compatibility issues with many tools (especially Module Federation, which expects node_modules). Berry (v3+) has a steep learning curve. Classic yarn (v1) is in maintenance mode.
  • yarn (v1 classic): Mature and stable but no longer actively developed. No content-addressable store, no strict dependency isolation.
  • Bun: Fast but still maturing, not all Node.js APIs supported in Workers context, workspace support still evolving.