Pnpm basics - how to transition quickly

What is pnpm ?

A package manager that aims to be:

  • fast
  • efficient on node_modules size
  • strict

Let's break this down:

Due to efficient centralized store management, pnpm is expected to be up to 2x faster than npm.

  • pnpm writes one unique file only ever once on your disk
  • pnpm uses a different (very complex) algorithm to fetch and resolve the dependencies of your project

To understand better the motivation behind the pnpm, please refer to the official docs

Why pnpm is faster ?

The traditional way of installing dependencies with npm contains 3 stages:

  • resolving
  • fetching
  • linking

As a result, everything lands in the node_modules of your project. Npm proceeds with each stage at the time, so fetching has to wait for the resolving step and so on.

Pnpm on the other hand runs the installation stages separately for each dependency.
Once a package is resolved, it immediately starts to fetch it.

For more benchmarks, you can check the official pnpm benchmarks

Smaller node_modules

Packages files are stored in a central content-addressable store.
Any project can access this store. Only files, that are different between package versions will be added to the store.
Let’s consider the following versions of React:

  • React 17
  • React 18.2.0
  • React 18.3.0

If hypothetically, the file jsx-dev-runtime.js will have the same content in all of these 3 versions, pnpm will store the file once.
This approach greatly reduces the size of the node_modules, especially if you have multiple projects on your machine.

More strict than npm

Pnpm is stricter than npm.
When you install @testing-library/jest-dom with npm it will hoist all of its dependencies to the root of the node_modules.
Package @testing-library/jest-dom is shipped with a dependency lodash which, even if you don't use it in your project, can be still imported and used e.g

// it will work since lodash is a dependency of the `@testing-library/jest-dom`
import lodash from 'lodash'

Although it is a rare case scenario, I have seen it in production apps, it is bad because:

  • you have 0 control over the version of the lodash that @testing-library/jest-dom
  • lodash is not your dependency listed in the package.json
  • in case @testing-library/jest-dom removes lodash in the next version it will break your app

Lodash is hoisted because npm follows the flat module structure by default.

Let's take a look at how pnpm solves this problem:

  • pnpm add only direct dependencies of your app to the node_modules
  • your node_modules will only contain @testing-library/jest-dom in this example
  • your app will break if you attempt to import lodash like in the npm example
  • @testing-library/jest-dom will still have lodash to function properly. It will be stored in the node_modules/.pnpm directory

How to migrate

  1. install pnpm with the corepack
  2. rm -rf node_modules
  3. rm package-lock.json - pnpm will create its own lock-file pnpm-lock.yaml
  4. pnpm install
  5. update ci workflow workflow

I use Github and Gitlab configs from the docs and had no issues so far.
It's up to you whether to cache the store on CI.
An official list of the pnpm continuous integration

Typing pnpm in your terminal can be too verbose, you can always create an alias in shell config .zshrc alias pn=pnpm

Metrics

During my migration to pnpm, I was using the 9.1.0 version of the pnpm.
All of my checks were done with time npm/pnpm i on medium-sized Qwik City blog with a ton of mdx, and a fairly standard amount of dependencies. Results showing average from the 5 runs of each command.

Without node_modules:

  • total time npm: 3.374s
  • total time pnpm: 1.701s

With node_modules:

  • total time npm: 0.826s
  • total time pnpm: 0.591s

GitHub actions:

  • total install time npm: 16s
  • total install time pnpm: 13s Additionally pnpm has to be installed, in my case it takes 1-2 seconds on GitHub action

Size of the node_modules note that it's the only Qwik project on my computer:

  • with npm: 260MB node_modules/
  • with pnpm: 240MB node_modules/

Monorepo

This article won't cover how pnpm handles the monorepo setups.
I will cover it in an upcoming article, once I get more insights into it in my daily job.