Skip to main content

Work with PNPM

Rhapsody uses pnpm as its package manager.

Why PNPM?

As Rhapsody continues to evolve, we are moving toward a world where the codebase is organized more like a monorepo, with individual packages and apps. In an ideal state, each package and app would also manage its own dependencies. To accomplish this, we need to make use of a common monorepo construct called "workspaces" that allows us to define all the packages within a repo for the package manager to handle for us. npm@7 shipped with workspaces support but sadly its workspaces functionality still lags far behind the more established workspaces-aware package managers such as pnpm and yarn. Consequently, we looked beyond npm for a solution. We evaluated yarn, but found that the churn between its v1, v2, and v3 to be unhelpful. yarn is also leaning heavily into its PnP feature, which has its own compatibility issues and wasn't a solution we needed. pnpm has been in use for years at Salesloft in the Design System and Cross Platform Components repo (via rush) so we know it's reliable and effective. It's used heavily by other large organizations as well such as Microsoft, Prisma, and Wix as well as by large open-source projects such as Vue, Cycle.js, and Verdaccio. Further, it's monorepo management is top-notch and provides the building blocks we need to move forward with our architecture goals.

Setup

Run the following commands in the Rhapsody repo:

# Install pnpm globally
npm install -g pnpm
# Add configuration to your global .npmrc file
echo 'store-dir=~/src/.pnpm-store' >> ~/.npmrc
# Remove all old node_modules directories
pnpm -r exec rm -rf node_modules
# Install all dependencies via pnpm
pnpm install

Common CLI Commands

pnpm's CLI commands differ somewhat from npm's. Below are some common used commands:

  • pnpm install: install all dependencies
  • pnpm add <pkg>: add a new dependency or update an existing dependency
  • pnpm run <command>: run a given <command> listed in the package.json scripts
  • pnpm why <pkg>: see what dependencies use <pkg> and what versions are used
What about npx?

PNPM has two commands that replace npx: pnpm dlx and pnpm exec. To understand the difference, let's talk about npx for a moment. With npm@6, invoking npx with a package name would first look at the current project to see if that dependency was installed. If the dependency was already installed, it would invoke the binary for that dependency. If the dependency wasn't installed, it would download the dependency from the registry and invoke the binary. Starting with npm@7, this behavior changed slightly. npx no longer downloads dependencies from the registry by default. As a safety measure, npx defaults to only running locally installed dependencies and prompts the user if a download is required. To automatically download a dependency, one needs to pass the --yes flag. PNPM splits these two use cases into two commands:

  • pnpm exec <pkg>: run the binary for a dependency that's already installed
  • pnpm dlx <pkg>: download a dependency then run the binary

Pro Tips

  • In whatever shell you use, create an alias for p=pnpm to shorten typing out pnpm (or accidentally typing npm from motor memory).
  • You can omit "run" when running most commands with pnpm, similar to how yarn works. It will automatically run scripts when they don't collide with built-in CLI commands. For example, pnpm run qa 1 becomes pnpm qa 1 (or with the above tip p qa 1).
  • To delete all depencencies and install from scratch, run pnpm -w -r exec rm -rf node_modules then pnpm install. This will delete node_modules directories for every workspace package, including the root, before installing dependencies.
  • pnpm provides advanced filtering for running commands against a subset of the entire repo. This will become more valuable as the number of workspace packages in Rhapsody grows.

Resources