Why we stopped using Lerna for monorepos
Tags: javascript, productivity, node, programming
Published: 2022-10-31 15:25:06

*And what’s the new fashion?*

## Intro

I need to be honest: Lerna is my mentor to the concept of monorepo, and it opened a new door for me to manage big JavaScript projects. It also allows for a shared repo within the sole ecosystem (JavaScript and TypeScript, specifically) for both the frontend and backend.

Lerna is great. In the previous article, [TypeScript all-in-one: Monorepo with its pains and gains](https://dev.to/gaosun/typescript-all-in-one-monorepo-with-its-pains-and-gains-4ne8), I mentioned that we were using Lerna for executing commands and publishing workspace packages. But now, it’s time to say goodbye.

## The context

> ℹ️ **TL;DR** Our project [Logto](https://github.com/logto-io/logto) used [PNPM](https://pnpm.io/) + [Lerna](https://github.com/lerna/lerna) for dependency and monorepo management.

### Why did we need Lerna?

When Lerna was born ([v1.0.1](https://github.com/lerna/lerna/tree/v1.0.1), Dec 2015), the NPM official support for monorepo (or workspaces) was still far away (until Oct 2020 with NPM v7). There were several pain points for managing multiple JS packages in one repo:

- Import another package in the same repo (workspace) **without publishing it to NPM**
- Run scripts for multiple workspace packages **in the dependency order**
- Version and publish all workspace packages **by a reasonable strategy**

None of them sounds easy, and Lerna can cover all.

### The monkey-patch situation

However, Lerna does not include package manager features; thus, you still need to use NPM or something else. As you can imagine, this leads Lerna to have the “all-in-one” monorepo support to eliminate the gap across different package managers. In other words, redundancy.

I started to scaffold the project with Lerna + PNPM (not NPM) since I’m a newbie to monorepo, and using Lerna was the standard approach in my mind. But some discord popped up over time.

> 💡 When it has 20% redundancy, it is acceptable; when it turns to 90%, I have a solid feeling to replace it with something more specific.

#### 🔗 Workspace dependencies

PNPM has a built-in command to add workspace dependencies:

```bash
pnpm add @logto/some-package --workspace
```

You can use it in ANY workspace package. It will also add a dependency with [the special protocol](https://pnpm.io/workspaces#workspace-protocol-workspace) `workspace:` to the `package.json`.

Unfortunately, Lerna cannot recognize it. So we need to fall back to the command:

```bash
lerna add @logto/package-a --scope=@logto/package-b
```

This will add `package-a` as a dependency of `package-b`. But you must run it in the workspace root and specify the scope.

Plus, PNPM uses hard links to reuse and link dependencies, which makes `lerna bootstrap` unused (this is a core feature of Lerna).

#### 🏃 Run scripts

As I mentioned in the previous article:

> Now `pnpm` has a `-w` option to run commands in the workspace root and `--filter` for filtering. Thus you can probably replace `lerna` with a more dedicated package publishing CLI.

Besides, PNPM supports the `-r` option that allows you to run a script recursively, with dependency order support. Then we don’t need `lerna run` and `lerna exec` anymore, but Lerna has to keep the commands because not everyone uses PNPM.

P.S. I also love that PNPM doesn’t require adding additional `--` to pass down the options!

#### 🚀 Version and publish

Lerna’s opinionated and automatic versioning and publishing strategy is a significant advantage. It is extremely useful when you have like ten packages in the workspace. It will also help you tag and push to the remote git, even creating GitHub releases.

We’ll hold this part for now.

### The trigger

In fact, none of the things above triggered us to remove Lerna since our development was moving fast, and we thought the removal would naturally happen one day. When Lerna v6 was published, it finally pushed us to make the change.

The Nx team called the version a “[reborn](https://blog.nrwl.io/lerna-reborn-whats-new-in-v6-10aec6e9091c)” for Lerna, and yes, Lerna was declared “dead” in April 2022, and the team took over stewardship. This update also adds official support for PNPM.

Sounds really awesome, right? Soon we upgraded to v6 and found [an interesting issue](https://github.com/silverhand-io/configs/commit/ee5638a20b7a78d19db9e87ddeb1a42bb5f003c1#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb) with `lerna version`:

![Lerna v6 bug 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1y1h5ieqzy4ua2f0lu2z.png)

![Lerna v6 bug 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bdg3o1tbnphtjzh7b3z.png)

It will automatically update the quote marks (from single to double) and the indent style of `pnpm-lock.yaml`.

> ❔ So, no test for the lockfile changes and no bug report during the beta?

We think it’s time to say goodbye.

## Removing Lerna

Thanks to the powerful workspace features in PNPM, the process was easier than we expected.

### 🔗 Workspace dependencies

Everything still works without change, but updating all workspace dependency version numbers in `package.json` to the [workspace protocol](https://pnpm.io/workspaces#workspace-protocol-workspace) is highly recommended since it’s smarter and PNPM uses it for workspace dependencies by default. Don’t forget to run `pnpm i` to fix the lockfile.

From now, we can use `pnpm add @logto/some-package --workspace` right in the directory for which we want to add the workspace dependency.

### 🏃 Run scripts

Replace `lerna run foo` with `pnpm -r foo`.

That’s it. And for filtering? Not a problem for the [filtering option](https://pnpm.io/filtering).

### 🚀 Version and publish

PNPM can take care of publishing but not versioning. Here are several options:

#### Using Lerna

> ⚠️ Lerna (and NPM) doesn’t recognize the workspace protocol, so you cannot use `pnpm add --workspace` in this approach. Instead, you need to update `package.json` files for workspace dependency changes manually, then run `pnpm i` to fix the lockfile.

- **Details about the workspace protocol issue**
    
    Lerna will keep the `workspace:` protocol during versioning, which is fine. When publishing, Lerna invokes `npm publish` with a non-negotiable attitude. Unlike `pnpm publish`, `npm publish` won’t update the `workspace:` protocol to the proper version number when packing files, which will cause failed installations for your package users.
    

Yes, you can still version with Lerna without listing it in the dev dependencies. We used it as a transition plan in our workflow:

```diff
   run: |
-    pnpm lerna publish \
+    pnpm \
+    --package=conventional-changelog-conventionalcommits \
+    --package=lerna@^5.0.0 \
+    dlx lerna publish \
```

We are using `dlx` since our changelog was generated by the rules of [Conventional Commits](https://www.conventionalcommits.org/). If you don't need it, do this:

```diff
   run: |
-    pnpm lerna publish \
+    pnpx lerna@^5.0.0 publish \
```

#### Version with Changesets

I found a tool named [Changesets](https://github.com/changesets/changesets) for versioning and publishing monorepo in PNPM’s [docs](https://pnpm.io/using-changesets). After some study on its philosophy, I believe it’ll be a good fit for monorepos:

- Package-level version bumping with flexible strategy configuration
- Customizable linked and fixed packages for versioning (easily to achieve the same effect as Lerna)
- Shipped with a GitHub bot and workflow, and a workable publishing practice

It’s not perfect yet. For example, we still need to manually define the “publish group” in our repo and configure the GitHub workflow for creating releases. But it’s still a good tool that enables much more possibilities and another level of flexibility for monorepo publishing.

We are still exploring this approach, and I can write another article once we figure things out.

#### Write a version script

We didn’t try this, while it shouldn’t be too hard for the simplest implementation. Write a script for versioning all necessary packages and run it before `pnpm -r publish` and it will be ok. E.g., use the [“since” filter](https://pnpm.io/filtering#--filter-since) to version all changed packages since the last version.

## Closing notes

Our lockfile size [decreased dramatically](https://github.com/logto-io/logto/pull/2170/files) after removing Lerna, which didn’t affect our daily development. Actually, it’s hard to tell the difference, and I’m happy we could achieve the same dev experience with much fewer dependencies.

For the past year, our codebase has increased a lot, and we still feel manageable. I think monorepo deserves a big credit. If you are interested in more articles about monorepo, feel free to drop a comment to let us know!

> 👉 Our project [Logto](https://github.com/logto-io/logto) is an OSS that helps you build the sign-in, auth, and user identity within minutes.