pnpm vs Yarn: monorepo node_modules
Tags: pnpm, yarn, monorepo
Published: 2018-11-04 20:12:04

Both [pnpm](https://pnpm.js.org/) (as of v2.17) and [Yarn](https://yarnpkg.com/en/) (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](https://yarnpkg.com/lang/en/docs/workspaces/#toc-limitations-caveats):

> 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:

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

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

> See results [here](https://github.com/zkochan/comparing-monorepo-node-modules/tree/master/yarn-example)

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:

```ini
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](https://github.com/zkochan/comparing-monorepo-node-modules/tree/master/pnpm-example)

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](https://github.com/zkochan/comparing-monorepo-node-modules/tree/master/pnpm-example/foo), 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`](https://medium.com/pnpm/pnpms-strictness-helps-to-avoid-silly-bugs-9a15fb306308) but also in a multi-package repository.