loading...
Cover image for pnpm vs Yarn: monorepo node_modules

pnpm vs Yarn: monorepo node_modules

zkochan profile image Zoltan Kochan Originally published at Medium ・3 min read

Both pnpm (as of v2.17) and Yarn (as of v1.12) support fast, concurrent installations in monorepos. However, there is a big difference between how they store dependencies in monorepos. Yarn tries to hoist all dependencies from all workspace packages into the root node_modules of the monorepo, which means that packages have access to dependencies of other packages in the workspace. This is described in the Yarn docs:

Be careful when publishing packages in a workspace. If you are preparing your next release and you decided to use a new dependency but forgot to declare it in the package.json file, your tests might still pass locally if another package already downloaded that dependency into the workspace root. However, it will be broken for consumers that pull it from a registry, since the dependency list is now incomplete so they have no way to download the new dependency. Currently, there is no way to throw a warning in this scenario.

Like Yarn, pnpm hoists all packages to the root. However, pnpm stores all dependencies in a hidden folder and only links the direct dependencies of every package into their node_modules.

Let's see how Yarn and pnpm work on a simple example:

  • there is a small monorepo with two packages: foo and bar
  • foo has is-negative@1.0.0 as a dependency
  • bar has is-positive@1.0.0 as a dependency

Yarn in action

To make this repo work with Yarn, there should be a package.json in the root of the repo with the following content:

{
 "private": true,
 "workspaces": ["foo", "bar"]
}

To install all dependencies of all workspace packages, you should run yarn install.

See results here

In this case, Yarn will create a single node_modules in the root of the repo with both is-negative and is-positive. It means that both foo and bar will have access to the dependencies of each other.

pnpm in action

To make pnpm install dependencies in this repo, you'll need to create a pnpm-workspace.yaml in the root of the repo (it can be empty) and a .npmrc file with the following content:

shared-workspace-shrinkwrap = true
link-workspace-packages = true

To install all dependencies of all workspace packages with pnpm, you should run pnpm multi install (or just pnpm m i).

See results here

Like Yarn, pnpm creates a node_modules in the root of the repo. However, if you open that folder, you'll see that all directories and files are hidden there:

node_modules
+ .registry.npmjs.org
+ .modules.yaml
+ .shrinkwrap.yaml

This node_modules stores all the dependencies but they are hidden in .registry.npmjs.org. Node's resolution algorithm will not find them.

Now if you check the directory of bar, you'll see a node_modules there as well, with a single symlink called is-negative. This symlink is pointing at ../../node_modules/.registry.npmjs.org/is-negative/1.0.0/node_modules/is-negative. Likewise, there is a symlink to is-positive in the node_modules directory of foo. So foo is able to resolve only is-positive and bar is only able to resolve is-negative 🎉😊


As you can see, pnpm is strict not only when used with a single package.json but also in a multi-package repository.

Posted on by:

zkochan profile

Zoltan Kochan

@zkochan

European. Developer. Maintainer of pnpm. a.k.a. 🇺🇦Кочан Золтан🇺🇦 and 🇭🇺Kocsán Zoltán🇭🇺

Discussion

markdown guide