npm, Yarn, Yarn Berry, and pnpm

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.

ActionnpmYarn ClassicYarn Berrypnpm
install deps in package.jsonnpm 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

ActionnpmYarn ClassicYarn Berrypnpm
install packages globallynpm 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

ActionnpmYarn ClassicYarn Berrypnpm
publish packagenpm publishyarn publishyarn npm publishpnpm publish
list installed depsnpm 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