Editor’s note: This post was completely rewritten 16 February 2022 to reassess the package manager landscape, reference and compare new tools and workspaces, discuss Corepack functionality and performance impacts, provide a bird’s-eye view of package manager usage among popular open source projects, and more thoroughly explain the evolution of package managers since 2010.
Three major players exist in the field of package managers today:
Virtually, we’ve achieved feature-parity among all package managers, so most likely you’ll decide which package manager to use based on non-functional requirements, like installation speed, storage consumption, or how it meshes with your existing workflow.
Of course, how you choose to use each package manager will differ, but they all share a set of main concepts. You can do the following with any of these package managers:
Despite this parity, though, package managers differ under the hood. Traditionally, npm and Yarn have installed dependencies in a flat node_modules
folder. But this dependency resolution strategy is not free of criticism.
Thus, pnpm has introduced some new concepts to store dependencies more efficiently in a nested node_modules
folder. Yarn Berry goes even further by ditching node_modules
completely with its Plug’n’Play (PnP) mode.
In this article, we’ll compare these package managers across a variety of criteria, including installation workflows, project structures, config files, and more. Feel free to use the table of contents to skip around and read what’s most relevant to you.
I’ve created a companion React app to demonstrate some of the different package managers’ unique concepts. There exists a corresponding Git branch for every package manager variant. This is the project I also used to create the performance table in the below section of this post.
Although the type of application is not important for the topic of this article, I have chosen a mid-size and realistic project to be able to illuminate different aspects; as an example from the recent past, Yarn Berry’s PnP mechanism caused some heated discussions about compatibility issues that this project is suited to help examine.
The very first package manager ever released was npm, back 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 that have emerged:
node_modules
folder structures (nested vs. flat, node_modules
vs. PnP mode)Let’s dive into a brief history of how these needs were identified after npm rose to prominence, how Yarn Classic solved some of them, how pnpm has expanded on these concepts, and how Yarn Berry, as Yarn Classic’s successor, tried to break the mold set by these traditional concepts and processes.
npm is the forefather of package managers. Mistakenly, many people believe npm is an acronym for “Node package manager” but this is not the case. Nevertheless, it’s bundled with the Node.js runtime.
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.
In 2020, GitHub acquired npm, so in principle, npm is now under the stewardship of Microsoft. At the time of this writing, the most recent major version is v8, released in October 2021.
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:
Yarn v1 entered maintenance mode in 2020. Since then, the v1.x line has been considered legacy and was renamed to Yarn Classic. Its successor, Yarn v2 or Berry, is now the active development branch.
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.
The influence of pnpm can be seen in their 2021 report: competitors want to adopt pnpm’s installation concepts, like the symlinked node_modules
structure and the disk-efficient management of packages due to their innovations in content-addressable storage.
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.
All of this change, and so quickly, led to a great deal of controversy after release. PnP’s breaking changes required maintainers to update their existing packages in order to be compatible with it. The brand new PnP approach was used by default, and reverting to node_modules
was not initially straightforward, which led to many prominent developers openly criticizing Yarn 2 for not making it opt-in.
The Yarn Berry team has since tackled many issues in its subsequent releases. To address the incompatibility of PnP, the team offered some ways to easily change the default operation mode. With the help of a node_modules
plugin, just one line of configuration was needed to use the traditional node_modules
approach.
Additionally, the JavaScript ecosystem has provided more and more support for PnP over time, as you can see in this compatibility table, and some big projects have moved to adopt Yarn Berry. In my companion project, I was also able to properly implement PnP with my demo React project.
Although Yarn Berry is quite young, it, too, has already an impact on the package manager landscape — pnpm adopted a PnP approach in late 2020.
A package manager has to be installed on every developer’s local and CI/CD systems first.
npm is shipped with Node.js, so no extra step is needed. Besides downloading the Node.js installer for your OS, it has become common practice to use CLI tools for managing software versions. In the context of Node, Node Version Manager (nvm) or Volta have become very handy utilities.
You can install Yarn 1 in different ways, e.g., as an npm package with $ npm i -g yarn
.
To migrate from Yarn Classic to Yarn Berry, the recommended way is to:
yarn set version
command to upgrade to the latest modern version: $ yarn set version berry
However, the recommended way to install Yarn Berry is via Corepack.
Corepack was created by the folks of Yarn Berry. The initiative was originally named package manager manager (pmm) 🤯 and merged with Node in LTS v16.
With the help of Corepack, you don’t have to install npm’s alternative package managers “separately” because Node includes Yarn Classic, Yarn Berry, and pnpm binaries as shims. These shims allow users to run Yarn and pnpm commands without having to explicitly install them first, and without cluttering the Node distribution.
Corepack comes preinstalled with Node.js ≥ v16.9.0. However, for older Node versions, you can install it using $ npm install -g corepack
.
Enable Corepack first, before using it. The example shows how to activate it in Yarn Berry v3.1.1.
# you need to opt-in first $ corepack enable # shim installed but concrete version needs to activated $ corepack prepare [email protected] --activate
You can install pnpm as an npm package with $ npm i -g pnpm
. You can also install pnpm with Corepack: $ corepack prepare [email protected] --activate
.
In this section, you’ll see the main characteristics of the different package managers at glance. You can easily spot which files are involved in configuring particular package managers, and which files are generated by an installation step.
All package managers store all important meta information in the project manifest file, package.json
. Further, a config file at the root level can be used to set up private registries or dependency resolution methods.
With an install step, dependencies are stored in a file structure (e.g., within node_modules
) and a lock file is generated. This section does not take a workspaces setup into account, so all examples only show a single location where dependencies are stored.
With $ npm install
, or the shorter $ npm i
, a package-lock.json
file and a node_modules
folder are generated. An optional .npmrc
config file can be placed at the root level. See the next section for more information on lock files.
. ├── node_modules/ ├── .npmrc ├── package-lock.json └── package.json
Running $ yarn
creates a yarn.lock
file and a node_modules
folder. A .yarnrc
file can be also be a configuration option; Yarn Classic also honors .npmrc
files. Optionally, a cache folder (.yarn/cache/
) and a location storing the current Yarn Classic version (.yarn/releases/
) can be used. Different ways to configure this can be seen in the section comparing configurations.
. ├── .yarn/ │ ├── cache/ │ └── releases/ │ └── yarn-1.22.17.cjs ├── node_modules/ ├── .yarnrc ├── package.json └── yarn.lock
node_modules
Independent of the install mode, you’ll have to handle more files and folders in Yarn Berry projects than projects that use the other package managers. Some are optional and some are mandatory.
Yarn Berry no longer honors .npmrc
or .yarnrc
files; instead, a .yarnrc.yml
config file is required. For a traditional workflow with a generated node_modules
folder, you have to provide a nodeLinker
config that uses either node_modules
or pnpm-inspired installation variant.
# .yarnrc.yml nodeLinker: node-modules # or pnpm
Running $ yarn
installs all dependencies in a node_modules
folder. A yarn.lock
file is generated, which is newer but incompatible with respect to Yarn Classic. In addition, a .yarn/cache/
folder is generated used for offline installs. The releases
folder is optional and stores the version of Yarn Berry that is used by the project, as we’ll see in the section comparing configurations.
. ├── .yarn/ │ ├── cache/ │ └── releases/ │ └── yarn-3.1.1.cjs ├── node_modules/ ├── .yarnrc.yml ├── package.json └── yarn.lock
For both strict and loose PnP modes, executing $ yarn
generates .yarn/cache/
and .yarn/unplugged/
, along with .pnp.cjs
and yarn.lock
files. PnP strict is the default mode, but for loose, a config is required.
# .yarnrc.yml nodeLinker: pnp pnpMode: loose
In a PnP project, the .yarn/
folder will most likely contain an sdk/
folder to provide IDE support besides a releases/
folder. There are even more folders that can be part of .yarn/
, depending on your use case.
. ├── .yarn/ │ ├── cache/ │ ├── releases/ │ │ └── yarn-3.1.1.cjs │ ├── sdk/ │ └── unplugged/ ├── .pnp.cjs ├── .pnp.loader.mjs ├── .yarnrc.yml ├── package.json └── yarn.lock
The initial state of a pnpm project looks just like an npm or a Yarn Classic project — you need a package.json
file. After installing the dependencies with $ pnpm i
, a node_modules
folder is generated, but its structure is completely different because of its content-addressable storage approach.
pnpm also generates its own version of a lock file, pnp-lock.yml
. You can provide additional configuration with an optional .npmrc
file.
. ├── node_modules/ │ └── .pnpm/ ├── .npmrc ├── package.json └── pnpm-lock.yml
As described in the previous section, every package manager creates lock files.
Lock files store exactly the versions of each dependency installed for your project, enabling more predictable and deterministic installs. This is required because dependency versions are most likely declared with version ranges (e.g., ≥ v1.2.5) and, therefore, the actually-installed versions could differ if you don’t “lock down” your versions.
Lock files also sometimes store checksums, which we’ll cover in more depth in our section on security.
Lock files have been an npm feature since v5 (package-lock.json
), in pnpm from day one (pnpm-lock.yaml
), and in a new YAML format in Yarn Berry (yarn.lock
).
In the previous section, we saw the traditional approach, where dependencies are installed in a node_modules
folder structure. This is the scheme npm, Yarn Classic, and pnpm all use, wherein pnpm does it more efficiently than the others.
Yarn Berry in PnP mode does it differently. Instead of a node_modules
folder, dependencies are stored as zip files in combination of a .yarn/cache/
and .pnp.cjs
file.
It is best to have these lock files under version control because it solves the “works on my machine” problem — every team member installs the same versions.
The following tables compare a curated set of different CLI commands available in npm, Yarn Classic, Yarn Berry, and pnpm. This is by no means a complete list, but constitutes a cheat sheet. This section does not cover workspace-related commands.
npm and pnpm specially feature many command and option aliases, which means that commands can have different names, i.e., $ npm install
is the same as $ npm add
. Additionally, many command options have short versions, e.g., -D
instead of --save-dev
.
In the tables, I’ll refer to all short versions as aliases. With all package managers, you can add, update, or remove multiple dependencies by separating them with spaces (e.g., npm update react react-dom
). For the sake of clarity, examples only show usage with single dependencies.
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 |
The following examples show how to manage packages constituting utility tools during development time — aka binaries, such as ntl, to interactively execute scripts. The terminology used in the table:
node_modules/.bin/
or .yarn/cache/
(PnP)It is important to understand that Yarn Berry only allows us to execute binaries we’ve specified in our package.json
or that are exposed in your bin
meta field for security reasons. pnpm features the same security behavior.
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 |
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 |
This table covers useful inbuilt commands. If there is no official command, often a third-party command can be used, via an npm package or Yarn Berry plugin.
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 |
N/A | N/A | N/A |
Configuring package managers takes place in both your package.json
and dedicated config files. Examples for configuration options are:
Most configuration takes place in a dedicated config file (.npmrc
).
If you want to use npm’s workspaces feature, you have to add a configuration to the package.json
by using the workspaces metadata field to tell npm where to find the folders constituting sub-projects or workspaces, respectively.
{ // ... "workspaces": [ "hooks", "utils" ] }
Every package manager works out of the box with the public npm registry. In a company context with shared libraries, you’ll most likely want to reuse them without publishing them to a public registry. To config a private registry, you can do this in a .npmrc
file.
# .npmrc @doppelmutzi:registry=https://gitlab.doppelmutzi.com/api/v4/projects/41/packages/npm/
There exist many configuration options for npm, and they are best viewed in the docs.
You can setup Yarn workspaces in your package.json
. It is analogous to npm, but the workspace has to be a private package.
{ // ... "private": true, "workspaces": ["workspace-a", "workspace-b"] }
Any optional configurations go into a .yarnrc
file. A common configuration option is to set a yarn-path
, which enforces a particular binary version to be used by every team member. The yarn-path
directs to a folder (e.g., .yarn/releases/
) containing a particular Yarn version. You can install a Yarn Classic version with the yarn policies
command.
Configuring workspaces in Yarn Berry is also analogous to how it’s done in Yarn Classic, with a package.json
. Most Yarn Berry configuration takes place in .yarnrc.yml
, and there are many configuration options available. The Yarn Classic example is also possible, but the metadata field is renamed to yarnPath
.
# .yarnrc.yml yarnPath: .yarn/releases/yarn-3.1.1.cjs
Yarn Berry can be extended with plugins by using the yarn plugin import
. This command updates the .yarnrc.yml
.
# .yarnrc.yml plugins: - path: .yarn/plugins/@yarnpkg/plugin-semver-up.cjs spec: "https://raw.githubusercontent.com/tophat/yarn-plugin-semver-up/master/bundles/%40yarnpkg/plugin-semver-up.js"
As described in the history section, there might be problems with dependencies in PnP strict mode due to incompatibility. There is a typical solution for such a PnP problem: the packageExtensions
configuration property. You can follow the next example with the companion project.
# .yarnrc.yml packageExtensions: "styled-components@*": dependencies: react-is: "*"
pnpm uses the same configuration mechanism as npm, so you can use a .npmrc
file. Configuring a private registry also works the same way as with npm.
With pnpm’s workspaces feature, support for multi-package projects is available. To initialize a monorepo, you have to specify the location of the packages in a pnpm-workspace.yaml
file.
# pnpm-workspace.yaml packages: - 'packages/**'
A monorepo is a repository that houses multiple projects, which are referred to as workspaces or packages. It is a project organization strategy to keep everything in one place instead of using multiple repositories.
Of course, this comes with additional complexity. Yarn Classic was the first to enable this functionality, but now every major package manager offers a workspaces feature. This section shows how to configure workspaces with each of the different package managers.
The npm team released the long-awaited npm workspaces feature in v7. It contained a number of CLI commands that helped manage multi-package projects from within a root package. Most of the commands can be used with workspace-related options to tell npm if it should run against a specific, multiple, or all workspaces.
# Installing all dependencies for all workspaces $ npm i --workspaces. # run against one package $ npm run test --workspace=hooks # run against multiple packages $ npm run test --workspace=hooks --workspace=utils # run against all $ npm run test --workspaces # ignore all packages missing test $ npm run test --workspaces --if-present
In contrast to the other package managers, npm v8 doesn’t currently support advanced filtering or executing multiple workspace-related commands in parallel.
In August 2017, the Yarn team announced first-class monorepo support in terms of a workspaces feature. Prior to this point, it was only possible to use a package manager in a multi-package project with third-party software like Lerna. This addition to Yarn paved the way for other package managers to implement such a feature, too.
I’ve also written previously about how to use Yarn Classic’s workspaces feature with and without Lerna, if you’re interested. But this post will only cover some necessary commands to help you manage dependencies in a Yarn Classic workspaces setup.
# Installing all dependencies for all workspaces $ yarn # display dependency tree $ yarn workspaces info # run start command only for one package $ yarn workspace awesome-package start # add Webpack to package $ yarn workspace awesome-package add -D webpack # add React to all packages $ yarn add react -W
Yarn Berry featured workspaces from the beginning because its implementation was built upon Yarn Classic’s concepts. In a Reddit comment, a main developer of Yarn Berry gave a brief overview of workspace-oriented features, including:
$ yarn add --interactive
: makes it possible to reuse versions from other workspaces when installing a package$ yarn up
: updates a package across all workspaces$ yarn workspaces focus
: installs dependencies only for a single workspace$ yarn workspaces foreach
: runs a command on all workspacesYarn Berry makes heavy use of protocols, which can be used in either the dependencies
or devDependencies
fields of package.json
files. One of them is the workspace:
protocol.
In contrast to Yarn Classic’s workspaces, Yarn Berry explicitly defines that a dependency has to be one of the packages in this monorepo. Otherwise, Yarn Berry might try to fetch a version from a remote registry if the versions do not match.
{ // ... "dependencies": { "@doppelmutzi/hooks": "workspace:*", "http-server": "14.0.0", // ... } }
With its workspace:
protocol, pnpm facilitates monorepo projects similarly to Yarn Berry. Many pnpm commands accept options like --recursive
(-r
) or --filter
that are especially useful in a monorepo context. Its native filtering command is also a good supplement or replacement for Lerna.
# prune all workspaces pnpm -r exec -- rm -rf node_modules && rm pnpm-lock.yaml # run all tests for all workspaces with scope @doppelmutzi pnpm recursive run test --filter @doppelmutzi/
Performance is a crucial part of decision-making. This section shows my benchmarks based on one small and one medium-sized project. Here are some notes about the sample projects:
I performed measurements for three use cases (UC), once for each of our package manager variants. To find out about the detailed evaluation with explanations, take a look at the results for project 1 (P1) and project 2 (P2).
node_modules
or .pnp.cjs
node_modules
or .pnp.cjs
node_modules
or .pnp.cjs
I used the tool gnomon to measure the time an install consumes (e.g., $ yarn | gnomon
). In addition, I measured the sizes of generated files, e.g., $ du -sh node_modules
.
With my projects and my measurements, Yarn Berry PnP strict was the winner in terms of installation speed for all use cases and both projects.
Performance results for Project 1 | |||||||
---|---|---|---|---|---|---|---|
Method | npm v8.1.2 |
Yarn Classic v1.23.0 |
pnpm v6.24.4 |
Yarn Berry PnP loose v3.1.1 |
Yarn Berry PnP strict v3.1.1 |
Yarn Berry node_modules v3.1.1 |
Yarn Berry pnpm v3.1.1 |
UC 1 | 86.63s | 108.89s | 43.58s | 31.77s | 30.13s | 56.64s | 60.91s |
UC 2 | 41.54s | 65.49s | 26.43s | 12.46s | 12.66s | 46.36s | 40.74s |
UC 3 | 23.59s | 40.35s | 20.32s | 1.61s | 1.36s | 28.72s | 31.89s |
Files and size | package-lock.json : 1.3Mnode_modules : 467M |
node_modules : 397Myarn.lock : 504K |
pnpm-lock.yaml : 412Knode_modules : 319M |
yarn.lock : 540Kcache: 68M unplugged: 29M .pnp.cjs : 1.6M |
yarn.lock : 540Kcache: 68M unplugged: 29M .pnp.cjs : 1.5M |
node_modules : 395Myarn.lock : 540Kcache: 68M |
node_modules : 374Myarn.lock : 540Kcache: 68M |
Performance results for Project 2 | |||||||
---|---|---|---|---|---|---|---|
Method | npm v8.1.2 |
Yarn Classic v1.23.0 | pnpm v6.24.4 |
Yarn Berry PnP loose v3.1.1 |
Yarn Berry PnP strict v3.1.1 |
Yarn Berry node_modules v3.1.1 |
Yarn Berry pnpm v3.1.1 |
UC 1 | 34.91s | 43.26s | 15.6s | 13.92s | 6.44s | 23.62s | 20.09s |
UC 2 | 7.92s | 33.65s | 8.86s | 7.09s | 5.63s | 15.12s | 14.93s |
UC 3 | 5.09s | 15.64s | 4.73s | 0.93s | 0.79s | 8.18s | 6.02s |
Files and size | package-lock.json : 684Knode_modules : 151M |
yarn.lock : 268Knode_modules : 159M |
pnpm-lock.yaml : 212Knode_modules : 141M |
.pnp.cjs : 1.1M.pnp.loader.mjs : 8.0Kyarn.lock : 292K.yarn : 38M |
.pnp.cjs : 1.0M.pnp.loader.mjs : 8.0Kyarn.lock : 292K.yarn : 38M |
yarn.lock : 292Knode_modules : 164Mcache: 34M |
yarn.lock : 292Knode_modules : 156Mcache: 34M |
Here are the official benchmarks of the Yarn Berry team and of pnpm.
npm has been a bit too forgiving when it comes to working with bad packages, and has experienced some security vulnerabilities that directly affected many projects. For example, in version 5.7.0, when you executed the sudo npm
command on a Linux OS, it became possible to change the ownership of system files, rendering the OS unusable.
Another incident occurred in 2018 and involved the theft of Bitcoin. Basically, the popular Node.js package EventStream added a malicious dependency in its version 3.3.6. This malicious package contained an encrypted payload that tried to steal Bitcoin from the developer’s machine.
To help solve these issues, more recent npm versions use the SHA-512
cryptography algorithm in the package-lock.json
to check the integrity of the packages you install.
Overall, npm has done more and more to close their security gaps, especially those made more obvious when compared to Yarn.
Both Yarn Classic and Yarn Berry have verified the integrity of each package with checksums stored in yarn.lock
since the beginning. Yarn also tries to prevent you from retrieving malicious packages that are not declared in your package.json
during installation: if a mismatch is found, the installation is aborted.
Yarn Berry in PnP mode does not suffer from the security problems of the traditional node_modules
approach. In contrast to Yarn Classic, Yarn Berry improves the security of command execution. You can only execute binaries of dependencies that you have explicitly declared in your package.json
. This security feature is similar to pnpm, which I’ll describe next.
pnpm also uses checksums to verify the integrity of every installed package before its code is executed.
As we alluded to above, npm and Yarn Classic each have security issues due to hoisting. pnpm avoids this because its model doesn’t use hoisting; instead, it generates nested node_modules
folders that remove the risk of illegal dependency access. This means that dependencies can only access other dependencies if they are explicitly declared in package.json
.
This is especially crucial in a monorepo setup, as we discussed, because the hoisting algorithm can sometimes lead to phantom dependencies and doppelgangers.
I analyzed many popular open source projects to get an idea of which package managers are used nowadays by the “developer elite.” It was important for me that these projects are actively maintained and last updated recently. This might give you another perspective when choosing a package manager.
npm | Yarn Classic | Yarn Berry | pnpm |
---|---|---|---|
Svelte | React | Jest (with node_modules ) |
Vue 3 |
Preact | Angular | Storybook (with node_modules ) |
Browserlist |
Express.js | Ember | Babel (with node_modules ) |
Prisma |
Meteor | Next.js | Redux Toolkit (with node_modules ) |
SvelteKit |
Apollo Server | Gatsby | ||
Nuxt | |||
Create React App | |||
webpack-cli | |||
Emotion |
Interestingly, at the time of this writing, none of these open source projects uses a PnP approach.
The current state of package managers is great. We have virtually attained feature parity among all major package managers. But still, they do differ under the hood quite a bit.
pnpm looks like npm at first because their CLI usage is similar, but managing dependencies is much different; pnpm’s method leads to better performance and the best disk-space efficiency. Yarn Classic is still very popular, but it’s considered legacy software and support might be dropped in the near future. Yarn Berry PnP is the new kid on the block, but hasn’t fully realized its potential to revolutionize the package manager landscape once again.
Over the years, many users have asked about who uses which package managers, and overall, it seems folks are especially interested in the maturity and adoption of Yarn Berry PnP.
The goal of this article is to give you many perspectives to make a decision about which package manager to use on your own. I would like to point out that I do not recommend a particular package manager. It depends on how you weight different requirements — so you can still choose whatever you like!
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
8 Replies to "JavaScript package managers compared: npm, Yarn, or pnpm?"
Very helpful thank you!
Thank you very much. If you want to learn more about package managers, you can find my follow-up article here https://blog.logrocket.com/exploring-workspaces-other-advanced-package-manager-features/
Where does the new kid on the block, Bun fit it?
Great article, thanks 🙂
hi, i want to reprint your article and translate for Chinese, can i?
Hey, LogRocket editor here. Thanks for reading the blog. We ask that you please don’t republish this post; for now, our policy is that we do not approve translations on third-party sites. Thanks for understanding.
FYI, Next.js migrated from Yarn to pnpm May 28th:
https://github.com/vercel/next.js/pull/37259
Stable release: Jun 28, 2022, v12.2.0:
https://github.com/vercel/next.js/releases/tag/v12.2.0
hi Michael, good to know. I think it will be interesting to see in a year’s time which major projects have chosen which package manager.
Do you think pnpm will gain a larger market share?