npm, Yarn, Yarn Berry, and pnpm
A brief history of JavaScript package managers
The first package manager ever released was npm, in January 2010. It established the core principles of how package managers work today.
If npm has been around for over 10 years, why are there any alternatives at all? Here are some key reasons why they have emerged:
npm, the pioneer
npm is the forefather of package managers. Its release constituted a revolution because, until then, project dependencies were downloaded and managed manually. Concepts like the package.json file with its metadata fields (e.g., devDependencies), storing dependencies in node_modules, custom scripts, public and private package registries, and more, were all introduced by npm.
Yarn (v1 / Classic), responsible for many innovations
In an October 2016 blog post, Facebook announced a collaborative effort with Google and a few others to develop a new package manager that would solve the issues with consistency, security, and performance problems that npm had at the time. They named the alternative Yarn, which stands for Yet Another Resource Negotiator.
Though they based Yarn’s architectural design on many concepts and processes that npm established, Yarn had a major impact on the package manager landscape in its initial release. In contrast to npm, Yarn parallelized operations in order to speed up the installation process, which had been a major pain point for early versions of npm.
Yarn set the bar higher for DX, security, and performance, and also invented many concepts, including:
Native monorepo support
Cache-aware installs
Offline caching
Lock files
pnpm, fast and disk-efficient
Version 1 of pnpm was released in 2017 by Zoltan Kochan. It is a drop-in replacement for npm, so if you have an npm project, you can use pnpm right away!
The main problem the creators of pnpm had with npm and Yarn was the redundant storage of dependencies that were used across projects. Though Yarn Classic had speed advantages over npm, it used the same dependency resolution approach, which was a no-go for the creators of pnpm: npm and Yarn Classic used hoisting to flatten their node_modules.
Instead of hoisting, pnpm introduced an alternative dependency resolution strategy: content-addressable storage. This method results in a nested node_modules folder that stores packages in a global store on your home folder (~/.pnpm-store/). Every version of a dependency is physically stored in that folder only once, constituting a single source of truth and saving quite a bit of disk space.
This is achieved through a node_modules layout, using symlinks to create a nested structure of dependencies, where every file of every package inside the folder is a hard link to the store. The following diagram from the official documentation clarifies this.
Yarn (v2, Berry), reinvents the wheel with Plug’n’Play
Yarn 2 was released in January 2020 and was billed as a major upgrade from the original Yarn. The Yarn team began referring to it as Yarn Berry to make it more obvious that it was essentially a new package manager with a new code base and new principles.
The main innovation of Yarn Berry is its Plug’n’Play (PnP) approach, which came about as a strategy to fix node_modules. Instead of generating node_modules, a .pnp.cjs file with dependency lookup tables is generated, which can be processed more efficiently because it’s a single file instead of a nested folder structure. In addition, every package is stored as a zip file inside of the .yarn/cache/ folder, which takes up less disk space than the node_modules folder.
In my experience pnpm is the most effective tool for several reasons. Speed, space, and efficiency are highly valued, but moreover, pnpm provides more intuitively understandable error messages. Yarn Berry's PnP sounds promising but it's still a very young manager that hasn't been adopted by many developers.
Installation
npm
npm is shipped with Node.js, so no extra step is needed.
Yarn Classic and Yarn Berry
Install Yarn with the following command
$ npm i -g yarn
Markup
Copy
To migrate from Yarn Classic to Yarn Berry, the recommended way is to:
Install or update Yarn Classic to the latest 1.x version
Use the
yarn set version
command to upgrade to the latest modern version
$ yarn set version berry
Markup
Copy
However, the recommended way to install Yarn Berry is via Corepack. Corepack comes preinstalled with Node.js ≥ v16.9.0.
# install corepack using npm if Node.js<16.9
$ npm install -g corepack
# you need to opt-in first
$ corepack enable
# shim installed but concrete version needs to activated
$ corepack prepare yarn@3.1.1 --activate
Markup
Copy
pnpm
You can install pnpm as an npm package.
$ npm i -g pnpm
# You can also install pnpm with Corepack:
$ corepack prepare pnpm@6.24.2 --activate
Markup
Copy
CLI Commands
Dependency management
This table covers dependency management commands to install or update all dependencies specified in package.json
, or multiple dependencies by specifying them in the commands.
Action | npm | Yarn Classic | Yarn Berry | pnpm |
install deps in package.json | npm install |
alias: i
, add
| yarn install
or yarn
| like Classic | pnpm install
alias: i
|
| update deps in package.json
acc. semver | npm update
alias: up
, upgrade
| yarn upgrade
| yarn semver up
(via plugin) | pnpm update
alias: up
|
| update deps in package.json
to latest | N/A | yarn upgrade --latest
| yarn up
| pnpm update --latest
alias: -L
|
| update deps acc. semver | npm update react
| yarn upgrade react
| yarn semver up react
| pnpm up react
|
| update deps to latest | npm update react@latest
| yarn upgrade react --latest
| yarn up react
| pnpm up -L react
|
| update deps interactively | N/A | yarn upgrade-interactive
| yarn upgrade-interactive
(via plugin) | $ pnpm up --interactive
alias: -i
|
| add runtime deps | npm i react
| yarn add react
| like Classic | pnpm add react
|
| add dev deps | npm i -D babel
alias: --save-dev
| yarn add -D babel
alias: --dev
| like Classic | pnpm add -D babel
alias: --save-dev
|
| add deps to package.json
without semver | npm i -E react
alias: --save-exact
| yarn add -E react
alias: --exact
| like Classic | pnpm add -E react
alias: --save-exact
|
| uninstall deps and remove from package.json
| npm uninstall react
alias: remove
, rm
, r
, un
, unlink
| yarn remove react
| like Classic | pnpm remove react
alias: rm
, un
, uninstall
|
| uninstall deps w/o update of package.json
| npm uninstall
--no-save
| N/A | N/A | N/A |
Package Execution
The following examples show how to manage packages constituting utility tools during development time
Action | npm | Yarn Classic | Yarn Berry | pnpm |
install packages globally | npm i -g ntl |
alias: --global
| yarn global add ntl
| N/A (global removed) | pnpm add --global ntl
|
| update packages globally | npm update -g ntl
| yarn global upgrade ntl
| N/A | pnpm update --global ntl
|
| remove packages globally | npm uninstall -g ntl
| yarn global remove ntl
| N/A | pnpm remove --global ntl
|
| run binaries from terminal | npm exec ntl
| yarn ntl
| yarn ntl
| pnpm ntl
|
| run binaries from script | ntl
| ntl
| ntl
| ntl
|
| dynamic package execution | npx ntl
| N/A | yarn dlx ntl
| pnpm dlx ntl
|
| add runtime deps | npm i react
| yarn add react
| like Classic | pnpm add react
|
| add dev deps | npm i -D babel
alias: --save-dev
| yarn add -D babel
alias: --dev
| like Classic | pnpm add -D babel
alias: --save-dev
|
| add deps to package.json
without semver | npm i -E react
alias: --save-exact
| yarn add -E react
alias: --exact
| like Classic | pnpm add -E react
alias: --save-exact
|
| uninstall deps and remove from package.json
| npm uninstall react
alias: remove
, rm
, r
, un
, unlink
| yarn remove react
| like Classic | pnpm remove react
alias: rm
, un
, uninstall
|
| uninstall deps w/o update of package.json
| npm uninstall
--no-save
| N/A | N/A | N/A |
Common commands
Action | npm | Yarn Classic | Yarn Berry | pnpm |
publish package | npm publish | yarn publish | yarn npm publish | pnpm publish |
list installed deps | npm ls |
alias: list
, la
, ll
| yarn list
| | pnpm list
alias: ls
|
| list outdated deps | npm outdated
| yarn outdated
| yarn upgrade-interactive
| pnpm outdated
|
| print info about deps | npm explain ntl
alias: why
| yarn why ntl
| like Classic | pnpm why ntl
|
| init project | npm init -y
npm init
(interactive)
alias: --yes
| yarn init -y
yarn init
(interactive)
alias: --yes
| yarn init
| pnpm init -y
pnpm init
(interactive)
alias: --yes
|
| print licenses info | N/A (via third-party package) | yarn licenses list
| N/A (or via plugin, other plugin) | N/A (via third-party package) |
| update package manager version | N/A (with third-party tools, e.g., nvm) | with npm: yarn policies set-version 1.13.0
| with Corepack: yarn set version 3.1.1
| N/A (with npm, Corepack) |
| perform security audit | npm audit
| yarn audit
| yarn npm audit
| pnpm audit
|
| add deps to package.json
without semver | npm i -E react
alias: --save-exact
| yarn add -E react
alias: --exact
| like Classic | pnpm add -E react
alias: --save-exact
|
| uninstall deps and remove from package.json
| npm uninstall react
alias: remove
, rm
, r
, un
, unlink
| yarn remove react
| like Classic | pnpm remove react
alias: rm
, un
, uninstall
|
| uninstall deps w/o update of package.json
| npm uninstall --no-save
| N/A | N/A | N/A |
To properly use yarn, do the following steps:
1. Go to the folder which contains the package.json
file (ClientApp angular / Folder Root in REACT)
2. First use npm install --package-lock-only
: This will create a package-lock.json
file, but will not make the node_modules
folder and download dependencies.
3. Use npm audit
to check for vulnerabilities.
4. If the audit fails use npm audit fix
. Use --force
to force the package manager to get references from the internet. Use --legacy-peer-deps
to use older versions of packages.
5. Use yarn import
to import all packages from package-lock.json
to yarn.lock
6. Use the yarn
command to create node_modules
and populate it with dependencies.
In case of failure, you can delete node_modules
, package-lock.json
, yarn.lock
and redo steps.
How to use Selective Version Resolution in YARN
1. Add a resolutions
field to your package.json
file and define your version overrides:
{
"name": "project",
"version": "1.0.0",
"dependencies": {
"left-pad": "1.0.0",
"c": "file:../c-1",
"d2": "file:../d2-1"
},
"resolutions": {
"d2/left-pad": "1.1.1",
"c/**/left-pad": "^1.1.2"
}
}
2. Run yarn to install
How to use Selective Version Resolution in NPM
This is a workaround to use Selective Version resolution without using yarn.
1. Add a field resolutions
with the dependency version you want to fix to your package.json
"resolutions": {
"hoek": "4.2.1"
}
2. Add npm-force-resolutions to the preinstall script so that it patches the package-lock
file before every npm install
"scripts": {
"preinstall": "npx npm-force-resolutions"
}
3. Run npm install
Subscribe to my newsletter
Read articles from Husain Salah directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by