Pnpm basics - how to transition quickly
What is pnpm ?Link to heading: 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 ?Link to heading: 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_modulesLink to heading: 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 npmLink to heading: 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
lodashthat@testing-library/jest-dom lodashis not your dependency listed in thepackage.json- in case
@testing-library/jest-domremoveslodashin 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-domin this example - your app will break if you attempt to import
lodashlike in the npm example @testing-library/jest-domwill still havelodashto function properly. It will be stored in thenode_modules/.pnpmdirectory
How to migrateLink to heading: How to migrate
- install pnpm with the corepack
- rm -rf node_modules
- rm
package-lock.json- pnpm will create its own lock-filepnpm-lock.yaml - pnpm install
- 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
MetricsLink to heading: 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:
13sAdditionallypnpmhas 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/
MonorepoLink to heading: 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.