Understanding package dependencies within a pnpm monorepo
Published: 2023-04-05 12:35:27

When you're working on a large project with multiple packages, a monorepo structure can help simplify your development workflow. With a monorepo, you can manage multiple packages as a single entity, which can make it easier to share code and manage dependencies. In this post, we'll take a look at how to structure dependencies in a pnpm monorepo, with examples to help illustrate the concepts.

### What is pnpm?
Before we dive into how to structure dependencies in a pnpm monorepo, let's first define what pnpm is. pnpm is a package manager for Node.js that optimizes the installation and sharing of packages across multiple projects. Unlike other package managers like npm and Yarn, pnpm uses a single global cache for all packages, which reduces disk usage and speeds up package installation.

### What is a monorepo?
A monorepo is a codebase that contains multiple packages or applications. Rather than storing each package or application in its own repository, a monorepo stores all packages or applications in a single repository. This can make it easier to manage dependencies, share code, and coordinate changes across packages.

In a monorepo, packages are often organized into a packages directory, with each package stored in its own subdirectory. Each package typically has its own `package.json` file, which lists its dependencies, devDependencies, and scripts.

## Structuring dependencies in a pnpm monorepo
When you're working with a pnpm monorepo, there are three types of dependencies that you need to be aware of: `dependencies`, `peerDependencies`, and `devDependencies`.

```json
// package.json
{
  "dependencies": {
    ...
  },
  "peerDependencies": {
    ...
  },
  "devDependencies": {
    ...
  }
```

Let's take a look at each one.

### dependencies
`dependencies` are packages that your package depends on to function properly. When you install a package's dependencies, pnpm will install them in the package's node_modules directory.

If a dependency is used in multiple packages in the monorepo, it should be listed as a regular dependencies in the `package.json` files of those packages. However, if a dependency is only used by a single package in the monorepo, it should be listed in that package's `package.json` file and should not be listed in the root directory's `package.json` file.

For example, let's say we have a monorepo with the following packages:

```
my-monorepo/
  packages/
    app/
      package.json
    ui-library/
      package.json
```

If both the `app` and `ui-library` packages are using the same version of `lodash`, it makes sense to list `lodash` as a regular dependency in the `package.json` files of both packages:

```json
// app/package.json
{
  "name": "app",
  "version": "1.0.0",
  "dependencies": {
    "lodash": "^4.17.21"
  }
}

// ui-library/package.json
{
  "name": "ui-library",
  "version": "1.0.0",
  "dependencies": {
    "lodash": "^4.17.21"
  }
}
```

On the other hand, if the `app` package is the only package that uses `react-router-dom`, we would only list `react-router-dom` as a dependency in the app package's `package.json` file:

```json
// app/package.json
{
  "name": "app",
  "version": "1.0.0",
  "dependencies": {
    "react-router-dom": "^5.3.0"
  }
}
```

### peerDependencies
`peerDependencies` are packages that your package depends on but that will be provided by the consumer of your package. They are typically used when your package provides functionality that depends on a specific version of another package, but you don't want to include that package as a regular dependency because it will be provided by the consumer of your package.

**When you specify a package as a peerDependency in your `package.json` file, you are essentially saying that "this package requires that the consumer of my package provides this package".** The consumer of your package must then ensure that the required package is installed and available to your package when it is used.

For example, let's say we have a monorepo with the following packages:

```
my-monorepo/
  packages/
    app/
      package.json
    ui-library/
      package.json
```

Let's say the `app` package depends on `react` as a regular dependency, but the `ui-library` package provides a component that depends on a specific version of `react`. In this case, we would list react as a regular dependency in the app package's package.json file, and as a peerDependency in the `ui-library` package's package.json file:

```json
// my-monorepo/packages/app/package.json
{
  "dependencies": {
    "react": "^17.0.2"
  }
}

// my-monorepo/packages/ui-library/package.json
{
  "peerDependencies": {
    "react": "^17.0.0"
  }
}
```

This ensures that `react` is installed in the app package's node_modules directory as a regular dependency, and is also listed as a required peerDependency for the `ui-library` package. When a consumer installs the `ui-library` package, they will need to ensure that they have installed a compatible version of `react` in their own project, which will be used by the `ui-library` package's component that requires it.


### devDependencies
`devDependencies` are packages that are only used during development and testing, and are not required for the package to run in production. They are typically used for tools like testing frameworks, build tools, and code quality tools.

When you list a package as a devDependency in your `package.json` file, pnpm will install it in the package's node_modules directory, but it will not be included in the production build of the package.

For example, if you are using Jest as your testing framework, you would list it as a devDependency in your `package.json` file:

```json
{
  "name": "my-package",
  "version": "1.0.0",
  "devDependencies": {
    "jest": "^27.2.1"
  }
}
```

In a pnpm monorepo, if a devDependency is used in multiple packages in the monorepo, it should be listed as a regular devDependencies in the `package.json` files of those packages. However, if a devDependency is only used by a single package in the monorepo, it should be listed in that package's `package.json` file and should not be listed in the root directory's `package.json` file.

### Conclusion
When you're working with a pnpm monorepo, it's important to understand how to structure your dependencies to ensure that your packages can share code and manage dependencies effectively. By using `dependencies`, `peerDependencies`, and `devDependencies` appropriately, you can create a clean and efficient monorepo structure that is easy to manage and maintain.

_Note: The logic regarding dependency structure described in this post applies to most monorepos in general, not just pnpm, however, different package managers may have different ways of managing dependencies and version ranges, so make sure to check the documentation._