How to migrate a JavaScript project to pnpm (package manager)
The pnpm package manager claims to be the “performant node package manager”! It saves disk space by only ever saving a single copy of a version of a package, and using hard links to reference the package in a project. Also, this enables it to resolve dependency trees much faster than its peers.
Should I migrate my existing projects?
The performance is noticeable better than npm, and if you use it consistently you will use less disk space.
Personally, I have too many projects on my system to migrate them all. If there is a smart way to automate the process, I would consider doing it in a more wholesale manner. I might selectively migrate some projects to reclaim some disk space in the meantime.
In terms of public projects, should you force users to use pnpm?
I think you should let it up to users where possible. There is some friction to doing this, because you should include a lock file to make installation a more predictable process for a particular package manager. You’re not going to maintain multiple lock files.
The command-line interfaces are quite similar, so you won’t get a major headache from switching. You will have to learn the differences. For example, to add a package for pnpm is pnpm add <package>
, whereas for npm it is npm install <package>
.
The other factor to consider is will pnpm be around in the long-term?
That is hard to predict! It is quite a popular project, with 18.6k stars on GitHub. It has some sponsorship. I think the future of npm and yarn are more assured because they are well funded. This is a common quandry in JavaScript-land really! I recall a package manager called tink that was moth-balled.
What is the difference between npm and yarn and pnpm?
All of them are package managers, but they vary in their approaches and feature sets. Although, the features seem to be converging over time.
npm was developed in the early days of Node.js and is the default package manager of Node.js. npm was designed around the idea of Semantic Versioning (semver). The dependencies of a project are stored in package.json. npm installs dependencies in a non-deterministic way, meaning that two developers could have a different node_modules
directory resulting into different behaviors.
To resolve those problems and others, Facebook introduced a new package manager in 2016 called Yarn . Yarn was touted as being faster, more secure, and more reliable than npm. Yarn consumed the same package.json
making it more straightforward to migrate. The differences between npm and Yarn were:
- You always know you’re getting the same thing on every development machine,
- It paralellizes operations that npm does not, and makes more efficient use of the network,
- It may make more efficient use of other system resources (such as RAM) as well.
However, npm narrowed the gap considerably. Since version 5 npm has done the following:
- npm now generates a ‘lockfile’ called
package-lock.json
that fixes your entire dependency tree much the same way the yarn (or any other) locking mechanism does, --save
is now implied fornpm i
,- It has improved network and cache usage.
The performance of Yarn is still superior to npm, but not by that much in many scenarios.
The main motivation behind pnpm was performance. It uses a content-addressable store to save disk space and to deliver quicker installation times.
Here are performance benchmarks from pnpm.io:
Here are the feature sets of the package managers:
Feature | pnpm | Yarn | npm |
---|---|---|---|
Workspace support | ✅ | ✅ | ✅ |
Isolated node_modules | ✅ - The default | ✅ | ❌ |
Hoisted node_modules | ✅ | ✅ | ✅ - The default |
Autoinstalling peers | ✅ - Via auto-install-peers=true | ❌ | ✅ |
Plug’n’Play | ✅ | ✅ - The default | ❌ |
Zero-Installs | ❌ | ✅ | ❌ |
Patching dependencies | ✅ | ✅ | ❌ |
Managing Node.js versions | ✅ | ❌ | ❌ |
Has a lockfile | ✅ - pnpm-lock.yaml | ✅ - yarn.lock | ✅ - package-lock.json |
Overrides support | ✅ | ✅ - Via resolutions | ✅ |
Content-addressable storage | ✅ | ❌ | ❌ |
Dynamic package execution | ✅ - Via pnpm dlx | ✅ - Via yarn dlx | ✅ - Via npx |
Side-effects cache | ✅ | ❌ | ❌ |
Installation of pnpm
You can follow the installation instructions on the official website.
I used their installation script as below:
And this was the output:
curl -fsSL https://get.pnpm.io/install.sh | sh -
==> Extracting pnpm binaries 7.6.0
Copying pnpm CLI from /tmp/tmp.iofxhAyyNc/pnpm to /home/rob/.local/share/pnpm/pnpm
No changes to the environment were made. Everything is already up-to-date.
You should be able to use the pnpm
command from the command-line now. Run pnpm --help
to check.
Migrate a project
Important! You need to keep in mind that pnpm
doesn’t use dependency hoisting by default:
When installing dependencies with npm or Yarn Classic, all packages are hoisted to the root of the modules directory. As a result, source code has access to dependencies that are not added as [direct] dependencies to the project.
By default, pnpm uses symlinks to add only the direct dependencies of the project into the root of the modules directory.
source: pnpm
This means if the package.json doesn’t reference a dependency that your code has a require()
or import
for, then it will fail to resolve. This is the biggest hurdle in the migration. You can use the auto-install-peers setting to do this automatically (disabled by default).
-
Run
pnpm import
. This generates apnpm-lock.yaml
based on the npm/yarn lockfile in the directory. Supported lock files:- package-lock.json
- npm-shrinkwrap.json
- yarn.lock
-
Clean up the files:
- Delete the node_modules folder in your project.
- Delete package-lock.json or yarn.lock.
-
Run
pnpm install
(alias ispnpm i
) to install the dependencies into a fresh node_modules folder. -
If it is a monorepo, a workspace must have a
pnpm-workspace.yaml
file in its root. You will need port the contents of the workspaces field from your package.json. -
Optional: If you want to ensure that pnpm must be used with the project, add the following script to package.json:
-
Optional: You can replace any mention of
npm run
withpnpm
in thescripts
section of the package.json. pnpm will figure it out if you don’t, so this can be skipped.