Lerna

Lerna #

Lerna provides a mechanism to run a command on each package in our mono repo with one cli. For example run test for all packages using only a command in the root of our repository:

yarn lerna run test

We start by adding lerna dependency at our workspace level:

yarn add -WD lerna

Now we need to configure lernain our repository. We do it by creating a lerna.json file in the root of our repository:

{
  "packages": ["packages/*"],
  "npmClient": "yarn",
  "version": "0.1.0",
  "useWorkspaces": true
}

We have defined our packages folder, and letting lerna knows we are using yarn and yarn workspaces. Here we are using a fixed version for the repository. Lerna also supports ìndependentversioning. For that we would need to change version to independent:

"version": "independent"

Lerna Versioning #

According to Lerna documentation, fixed versioning:

"Fixed mode Lerna projects operate on a single version line. The version is kept in the lerna.json file at the root of your project under the version key. When you run lerna publish, if a module has been updated since the last time a release was made, it will be updated to the new version you're releasing. This means that you only publish a new version of a package when you need to."

And independent versioning:

"Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it's a patch, minor, major or custom change."

Lerna Bootstrap #

Now, that we have Lerna configured we are ready to execute:

yarn lerna bootstrap

If we had dependencies between our existing packages, this would take care of "linking" everything up. It will be very useful for further developments, when we have packages depending on each other.

More about the bootstrap command here.

Now, that we have Lerna setted up we can start taking advantage of it. For example, lets execute the following command in the root of our repository:

yarn lerna run clean && yarn lerna run build

Each command will be executed in each package with one command. First we clean the build and then generate a new build.

Note: this command could be improved to use prebuild for clean task. We can also improve it with concurrent execution. I will leave it for later improvements. The goal here is to improve the Developer experience.

Other useful lerna commands to explore:

Internal dependencies with Lerna #

Lets imagine we want to create a booksService on utils creating booksService.ts file at packages/utils/src:

import { IBook } from "@mr/types";

const books: IBook[] = [
  {
    id: "1",
    title: "Clean Code",
    author: "Uncle Bob",
  },
  {
    id: "2",
    title: "The Pragmatic Programmer",
    author: "Andy Hunt and Dave Thomas",
  },
];

export function findBookById(id: string): IBook {
  const book = books.find(({ id: bookId }) => id === bookId);

  if (book) {
    return book;
  }

  throw new Error("Not Found");
}

export function allBooks(): IBook[] {
  return books;
}

Also add the following at packages/utils/src/index.ts:

export * from "./booksService";

We need to add @mr/types dependency to @mr/utils. With Lerna we could do the following:

yarn lerna add @mr/types --scope=@mr/utils

Experiment with:

yarn lerna run lint && yarn lerna run build

Next we need to publish our packages in an internal registry.

Published