Better Use pNPM
November 6th, 2023 | 4 mins readPublished on dev.to and medium.com
pNPM is a (another one) package manager for Node.
Ok, Ok!! There's already NPM (or better Yarn), so why change!? Well, for performance reasons, obviously ππ According to the metrics, pNPM is 2x faster than NPM π
Explanation
Like its counterparts, pNPM takes security very seriously by ensuring the integrity of dependencies used by one (or more *) JavaScript project(s), through a "lock" file strategy (pnpm-lock.yaml
).
* NB: Just as NPM (and Yarn before it), pNPM supports monolithic projects, or more commonly called: "workspaces".
What we appreciate the most about pNPM is the way it handles node_modules
. In fact, the big difference in using this package manager lies in the organization of dependencies.
NPM (and Yarn) have a "flat" node_modules
tree; in other words, all libraries are on the same level... pNPM works differently, listing dependencies by hard and symbolic links.
Moreover, pNPM keeps all libraries in a single location on disk: the "store". So, even if you fetch several versions of the same dependency, pNPM takes care of differentiating between them, and then only retrieves the necessary changes.
NB: Remember, with NPM, you fetch all dependencies when installing a project, and that for each project. As a result, each version is stored independently, taking up more memory space... The pNPM store isn't only efficient, it also saves a lot of disk space.
This mode of operation makes pNPM more robust than its rivals, since it is not allowed to use an unsaved dependency in the package.json
file.
In fact, with a traditional package manager, it's possible to use libraries implicitly, as long as they are retrieved from another dependency.
Use Case
When creating a new project with the "official" React CLI tool (for now): npx create-react-app my-project
; most of the project libraries are embedded in the react-scripts
dependency. This makes it possible to use ESLint or Jest implicitly. β οΈ SPOILER ALERT: this is not good practice!!!
This kind of use can create major problems for future development/application maintenance... For exemple, if react-scripts
updates one or other of the libraries, the behavior of your unit tests (i.e. Jest) will be affected. Even worse, what would be the consequence of removing Jest (in favor of another test runner)? That's why, the use of "transitive" dependencies isn't recommended! By the way, this is a part of the left-pad's story...
NB: To reproduce a resilience mechanism (similar to pNPM) for NPM or Yarn, you can use dependency-check to ensure that your project is compliant.
Migration
Convinced by this introduction? Then it's time to get down to business! The first step to migrate from NPM (or Yarn) to pNPM is installing the package manager: npm i -g pnpm
(alternative methods exist).
Then, into existing project, which already has a package-lock.json
file (or Yarn equivalent), just follow the instruction: pnpm import
.
Finally, you can delete your node_modules
and retrieve them (again) from pNPM: pnpm install
NB: Overall, pNPM is based on the NPM API, so the
pnpm install
,pnpm config set
andpnpm run your:script
commands are fully supported!
Let's take a look at the following package.json
, and compare the before/after using the new package manager:
{
"name": "react-only",
"version": "0.0.1",
"description": "React + DOM (Only)",
"main": "index.js",
"scripts": {
"start": "echo \"Β―\\_(γ)_/Β―\" && exit 0"
},
"dependencies": {
"react": "~18.2.0",
"react-dom": "~18.2.0"
},
"license": "Beerware"
}
When installing the above dependencies with NPM, you'll end up with a "flat" structure for your project:
/
βββ node_modules/
β βββ js-token
β βββ loose-envify
β βββ react
β βββ react-dom
β βββ scheduler
βββ package.json
βββ package-lock.json
On the other hand, when using pNPM to install react
and react-dom
, all dependencies (including indirect dependencies) are retrieved inside the hidden .pnpm
folder (and identified by version); only project-specific libraries are present at the root level of node_modules
(by a symbolic link). So, it's impossible to call js-token
, loose-envify
or scheduler
directly!
/
βββ node_modules/
β βββ .pnpm/
β β βββ js-token@4.0.0
β β βββ loose-envify@1.4.0
β β βββ react@18.2.0
β β βββ react-dom@18.2.0
β β βββ scheduler@0.23.0
β βββ react -> .pnpm/react@18.2.0/node_modules/react
β βββ react-dom -> .pnpm/react-dom@18.2.0/node_modules/react-dom
βββ package.json
βββ pnpm-lock.yaml
After that, the choice of pNPM seems obvious, if only for the readability of node_modules
. Don't worry, you won't be the first to migrate to this next-generation package manager, projects such as Prisma, Vue or even SvelteKit have done it, as well as Microsoft. Enjoy π