Using Lerna to manage your JavaScript monorepo
Tags: lerna, monorepo, npm
Published: 2020-02-26 14:34:53

---
title: Using Lerna to manage your JavaScript monorepo
published: true
description: Lerna helps remove the monotony and confusion out managing repos with many standalone components. Check it out!
tags: lerna, monorepo, npm
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/rnecclsgavssmfshc9zj.png
---

Let’s dig into Lerna!

[Lerna](https://lerna.js.org/) is a handy command line utility that you can use to manage JavaScript projects with multiple packages. It can be handy for both open source and private projects.

This post is intended to be more of a primer, and not a deep dive. That being said, if you have any suggestions to make this post better, or just have a question, please leave a comment below!

### What’s a package?

Projects often contain many components; perhaps a front-end, server, maybe some micro services, you get the point. They can be individually worked on, versioned, and released, all while being housed under the same repository, which in turn makes development access and project tracking easier. Think of these standalone components as your packages.

### How does Lerna help?

Lerna uses Git and NPM to create an optimal workflow for your many project packages, via the [`lerna`](https://www.npmjs.com/package/lerna) CLI. This could include versioning, distribution, testing, and so much more.

This tool is exciting to me because it removes the hassle of having to manually go through each package of a project to perform mundane operations. On the surface it’s a fairly simple tool, but it can be configured in some pretty neat ways, and used effectively can make working with a monorepo a huge time saver.

**Fun fact:**

The seven-headed monster you’ll see at the top of the project’s [website](https://lerna.js.org/) and [repo](https://github.com/lerna/lerna) depict the Greek mythological creature known as the Lernaean Hydra, or Hydra of Lerna. From the [Wikipedia article](https://en.wikipedia.org/wiki/Lernaean_Hydra):

> The Hydra possessed many heads, the exact number of which varies according to the source. Later versions of the Hydra story add a regeneration feature to the monster: for every head chopped off, the Hydra would regrow two heads.

I find this to be a pretty suitable mascot for the tool. 🐉

## Getting set up

As I mentioned above this tool has a few ways in which in can be configured to suit your needs, but in this post we’re just going to get up and running without too much trouble.

Before you go any further, install the command line utility globally:

```zsh
# via npm
npm install -g lerna
# via yarn
yarn global add lerna
```

Now you’ll need to decide how and where you’d like to set it up. Lerna can be initialized in a bare directory, or it can be added to an existing project. Either way, run `lerna init` in your project folder and you’ll get the following additions:

```zsh
lerna.json
package.json
/packages
```

If you ran it on a bare directory it’ll run `git init` for you. Make sure you add a remote so you can push versions up.

If you already had a `package.json` it won’t overwrite it, but it will add `lerna` as a dev dependency. Be sure to `npm install` afterward.

The newly added `packages/` dir will serve as the place for all your packages to live. “packages” is just the default. Speaking of which…

Your new `lerna.json` is the configuration that will be used when performing commands. You can see a [full list of options here](https://github.com/lerna/lerna#lernajson), but let’s run through a few:

- `packages` This tells Lerna where to find the packages it can operate on. By default this is `["packages/*"]`, which globs any directory under `packages/`, hence why it was auto-generated.
- `version` This value tells Lerna which version of the project we’re at. By default this is in “fixed” mode which uses a [semver](https://semver.org/) value and auto-increments when you run version commands, but you can optionally change this value to `independent` if you’d like to manage package versions independent from one another. You can set this automatically by running `lerna init` with `--independent`.
- `npmClient` The client to run commands with. Defaults to `npm`, but you can change it to `yarn` if you’d like.
- `command.publish.ignoreChanges` This can be an array of globs that Lerna will ignore when looking for changes to generate a new version. Examples could be your README, CHANGELOG, any tests or benchmarks, etc.

And that’s mostly it! From here you can add new packages under `packages/`. If you initialized Lerna in an existing root-level project you may need to do some reconfiguring. Just make sure that each package has a valid `package.json` that Lerna can invoke with its commands. For example:

```zsh
/packages
  /cli
    index.js
    package.json
```

Note that Lerna does _not_ need to exist as a dependency in each package.

**Quick tip:**

You may feel more comfortable manually setting up your packages, but if you’re setting up new ones a great command to use is `lerna create [name]`. This will handle it all for you by creating the appropriate structure and assisting you in creating a `package.json` (same as `npm init`).

## Commonly used commands

I went over `lerna init` and `lerna create` above to help you get started, so now let’s go over some of the commands you might use in a regular development workflow.

Note that commands support [filter options](https://www.npmjs.com/package/@lerna/filter-options), such as `--scope` to only execute on certain packages, `--no-private` to exclude private packages, and `--since [ref]` to only run on packages that have seen changes since a specified Git ref.

### [`lerna run`](https://github.com/lerna/lerna/tree/master/commands/run#lernarun)

This command will run, in each package, the script name affixed to it with the configured `npmClient`.

Let’s look at an example from the [Firefox Accounts monorepo](https://github.com/mozilla/fxa):

```json
// package.json
"scripts": {
  "format": "lerna run test",
}

// packages/fxa-content-server/package.json
"scripts": {
  "test": "node tests/intern.js --unit=true",
}

// packages/fxa-payments-server/package.json
"scripts": {
  "test": "npm-run-all test:*",
}

// packages/fxa-payments-server/package.json
"scripts": {
  "test": "scripts/test-local.sh",
}
```

These are just a handful of the packages listed in that repo, but you get the point. Now, when you run `npm run test` at the root, Lerna will iterate across each package and effectively run `npm run test` in each directory.

There are handful of ways to customize how this runs:

```zsh
# Pass args to the scripts
lerna run <script> -- [..args]
# Run scripts simultaneously, good for long-running processes 
lerna run <script> --parallel
# Ignore non-zero exit codes when running
lerna run --no-bail <script>
```

### [`lerna exec`](https://github.com/lerna/lerna/tree/master/commands/exec#readme)

Use this command to run an arbitrary command inside each package. This might seem familiar to the `run` command, and if you wanted to you could probably set it to perform the same operation, but this command will run _anything_, and not just an NPM script.

Take a look at how Antony Budianto’s [Create React App Universal CLI](https://github.com/antonybudianto/cra-universal) uses it:

```json
// package.json
"scripts": {
  "clean:build": "lerna exec -- rimraf lib",
  "demo:start:client": "lerna exec --scope cra-universal-demo -- npm start",
}
```

You can see that for all packages running `npm run clean:build` would execute the command `rimraf lib` in each package directory, and for subsequent commands like `demo:start:client` the execution of `npm start` is using the `--scope` [filter option](https://www.npmjs.com/package/@lerna/filter-options) to run only in the package named `cra-universal-demo`.

### [`lerna version`](https://github.com/lerna/lerna/tree/master/commands/version#readme)

This command performs a version bump on packages that have changed since the last release. It won’t run on any packages that have not seen changes, and if you’ve configured your `ignoreChanges` it will ignore those files as well.

By default just running `lerna version` will provide you with prompts as to how you’d like to perform each version bump. You can also provide a semver keyword (e.g. `prerelease`) or an explicit value (e.g. `1.4.8`).

Once that’s done Lerna will run lifecycle scripts, commit and tag the changes, and then push everything to your Git remote.

You can configure this command in a [variety of ways](https://github.com/lerna/lerna/tree/master/commands/version#options). For example:

```zsh
# Create a release on GitHub or GitLab
lerna version --create-release github
# Perform the changes on the current commit and don't push
lerna version --amend
# Use Convention Commits Specification to determine the version bump and generate CHANGELOG.md files
# https://www.conventionalcommits.org/en/v1.0.0/
lerna version --conventional-commits
# Provide your own version bump message, where %v is replaced with the new version
lerna version -m "chore(release): publish %v"
```

### [`lerna publish`](https://github.com/lerna/lerna/tree/master/commands/publish#readme)

You might be able to guess what this one does. As the name suggests, this command will publish a new version of each package to the NPM registry. By default it will call the `version` command behind the scenes and publish updates since the last release, but there are a couple other options for this:

```zsh
# Publish packages that have changed since the last release
lerna publish
# Explicitly publish packages tagged in the current commit
lerna publish from-git
# Handy for when you’d like to manually increment package versions but want to be able to automate publishing
# Explicitly publish packages where the latest version is not present in the registry
# Handy for when you need to retry a publish (e.g. it previously failed to publish)
lerna publish from-package
```

This too comes with a [handful of options](https://github.com/lerna/lerna/tree/master/commands/publish#options) to suit your needs. Some examples include:

```zsh
# Publish to NPM with a given dist-tag (instead of `latest`)
# https://docs.npmjs.com/cli/dist-tag 
lerna publish --dist-tag next
# If you have two-factor authentication set up to publish
lerna publish --otp 818391
# Automatically accept prompts that come up during the publish process
lerna publish --yes
```

## Wrapping up

I hope I was able to show you just how cool Lerna is in this post. It’s something I’m looking forward to using in all my projects with multiple components.

If you're in need of some examples of how others are using Lerna, the [Lerna website](https://lerna.js.org/#users) has a nice big list of repos, including [Jest](https://github.com/facebook/jest), [Chrome Workbox](https://github.com/GoogleChrome/workbox), [WordPress Gutenberg](https://github.com/WordPress/gutenberg), [Vue CLI](https://github.com/vuejs/vue-cli), [ESLint Typescript](https://github.com/typescript-eslint/typescript-eslint), and more.

If this post was interesting and you’re eager to Lerna more (sorry) from the DEV community, check out these fine posts from others:

- [What is a mono-repository and why you should try Lerna](https://dev.to/anonimoconiglio/what-is-a-mono-repository-and-why-you-should-try-lerna-57lm)
- [Why Lerna and Yarn Workspaces is a Perfect Match for Building Mono-Repos: A Close Look at Features and Performance](https://dev.to/doppelmutzi/why-lerna-and-yarn-workspaces-is-a-perfect-match-for-building-mono-repos-a-close-look-at-features-and-performance--1me0)
- [The highs and lows of using Lerna to manage your JavaScript projects](https://dev.to/chriis/the-highs-and-lows-of-using-lerna-to-manage-your-javascript-projects-3bgg)
- [pnpm vs Lerna: filtering in a multi-package repository](https://dev.to/zkochan/pnpm-vs-lerna-filtering-in-a-multi-package-repository-587i)

Also, if you’ve got 13 minutes you should definitely watch [this great video](https://www.youtube.com/watch?v=p6qoJ4apCjA) from Ben Awad where he demonstrates using Lerna. It helped me get a better grasp on how things work, and I’m sure I’ve used some of that info in this post.

I’d love to see how you’re using Lerna! If you’ve got any comments or questions, hit me up in the comments 😎.