Monorepo Javascript Projects with Yarn Workspaces and Lerna
Tags: javascript
Published: 2022-02-09 22:20:59

*This article was originally written by [Saiharsha Balasubramaniam](https://www.honeybadger.io/blog/monorepo-yarn-workspace-lerna/#authorDetails) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/monorepo-yarn-workspace-lerna/).*

Monorepo is a software development strategy in which a single repository contains code for multiple projects with shared dependencies. It has a number of advantages:

- It is easier to manage dependencies in monorepo projects. Common dependency versions are used, which saves a lot of time and computational power.
- It is easier to refactor code across all packages.
- Reusability of code is ensured.

And, like everything else in the world, the monorepo approach has certain disadvantages:

- Including multiple versions of a dependency in different packages might cause dependency conflicts.
- It degrades performance in version control systems, such as Git,  due to higher memory usage.
- Higher chances of merge conflicts.
- Initial setup takes a long time.

## Tools Used to Set Up a Monorepo Project

- [Lerna](https://lerna.js.org/) is used to optimize the management of monorepos. We'll use this tool to manage shared dependencies.
- [Yarn Workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) is used to optimize and link different packages together.
- [Storybook](https://storybook.js.org/) is used to build and test UI components.

### Lerna

Lerna is a tool used to manage monorepos. The repositories are structured into sub repositories. It is typically used in large codebases for shared dependency management and code deployment. Lerna has two major features, namely bootstrap and publish.

```bash
lerna bootstrap
```

This is a command provided by Lerna that does the following:

- It installs the dependencies of all the packages within the monorepo.
- It creates links between shared dependencies so that the same package is not installed twice.

```bash
lerna publish
```

The `publish` command publishes the package updated since the last version release.

### Yarn workspaces

Yarn workspaces are used to optimize dependency management. When we use yarn workspaces, all project dependencies are installed in one go. Tools like Lerna make use of Yarn workspaces' low-level primitives.

### Using Yarn Workspaces

Let us assume that we have two repositories, namely packages/repo-a and packages/repo-b, within our monorepo structure. To use workspaces, add the following to the `package.json` file of the root repository.

```json
{
  "private": true,
  "workspaces": ["packages/*"]
}
```

This adds all the folders within packages as a Yarn workspace. Now, if we run `yarn install`, dependencies of both `repo-a` and `repo-b` are installed.

## Setting up Your Project

We will be using yarn as a package manager. To set up Yarn in your machine, install it from the official [yarn website](https://classic.yarnpkg.com/en/docs/install#windows-stable).

Let us create a `package.json` for our project:

```json
{
  "name": "monorepo",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["packages/*"]
}
```

The `workspaces` option is used to specify which subfolder contains the various packages in our monorepo. Each folder within **packages** will be considered a separate project.

Now, let us set up Lerna as a developer dependency of our project. Create a new folder called monorepo. Open a terminal window and enter the following command:

```bash
yarn add lerna -D -W # add lerna as developer dependency, in the workspace root
yarn lerna init
```

This initializes a `lerna.json` configuration file. This file contains configuration parameters through which we can set up commands for various tasks. We can also define which package manager Lerna uses, such as npm or yarn. The above command also initializes a `package` folder where the projects can be located. In the `lerna.json` file, add the `npmClient` option to specify yarn as the package manager.

```json
{
  "packages": ["packages/*"],
  "npmClient": "yarn",
  "version": "0.0.0",
  "useWorkspaces": true
}
```

We have successfully set up the boilerplate for our monorepo. Now, let us set up a UI component library and a framework for testing the UI component library.

```bash
cd packages
mkdir monorepo-storybook && cd monorepo-storybook
yarn init
```

When you run `yarn init`, select all the default options. Let us install the required dependencies.

```bash
yarn add react react-dom
yarn add babel-loader -D
```

You may have noticed that the dependencies were not installed in a `node_modules` folder in the monorepo-storybook folder. Instead, it was installed within the `node_modules` folder in the root folder. This is how monorepos work with shared dependencies.

Now, let us configure storybook. Out storybook will be initialized, and the scripts required to install storybook will be configured.

```bash
npx sb init
```

Once it is configured, run the following script to start storybook:

```bash
yarn storybook
```

Some sample stories have been created for us. Let us explore and check out the storybook interface.

![Storybook Interface](https://www.honeybadger.io/images/blog/posts/monorepo-yarn-workspace-lerna/storybook-ui.jpg)
_Storybook's Interface_

Our storybook setup has been configured successfully. Now, let us create our component library. This will be under a different package. Under the `packages` folder, create a folder named `components` and then initialize the package by creating a `package.json` file.

_Note: Storybook isn't directly related to monorepos. It is just a framework for creating UI components. We are using Storybook to demonstrate the use of monorepos._

```json
{
  "name": "components",
  "version": "1.0.0"
}
```

Create a file named `Welcome.js`. Let us create a simple React component that displays a name, based on the prop passed to it.

```jsx
// Importing the react library
import React from "react";

export default function Welcome(props) {
  // Display the name passed as props
  return <h1>Hello, {props.name}</h1>;
}
```

Let us now initialize a `story` in storybook. Create a file called `Welcome.stories.js` within `monorepo-storybook/stories` folder.

```jsx
// Importing the react library
import React from "react";
// The storiesOf API is used to display stories in storybook
import { storiesOf } from "@storybook/react";
// Importing our react component
import Welcome from "../../components/Welcome";
// Displaying the component
storiesOf("Welcome", module).add("Welcome component", () => (
  <Welcome name="Harsha"></Welcome>
));
```

The storiesOf API is used to create and display stories. Let us now check the browser. We can see that a new story is created, and our component is displayed.

![Storybook Output](https://www.honeybadger.io/images/blog/posts/monorepo-yarn-workspace-lerna/storybook-output.jpg)
_The component as viewed in Storybook_

## Conclusion

Let us recap what we've learned in this article.

- We learned about monorepo projects and how they are prominent in open-source projects.
- We discussed the pros and cons of using the monorepo structure in a project.
- We learned about various tools, such as Yarn Workspaces, Lerna, and Storybook, which we used to set up the monorepo project.
- We walked through the steps involved in creating the project.
- We learned how to set up Storybook and created a components library.

You can find the final code for everything we’ve discussed at the [following link](https://github.com/cyberShaw/monorepo-template).

## Further Reading

You can expand upon your knowledge by checking out the following resources. Happy learning!

- Yarn Workspaces: [Documentation](https://classic.yarnpkg.com/en/docs/workspaces/)
- Rush: An alternate monorepo tool: [Documentation](https://rushjs.io/pages/intro/welcome/)
- Build a design system using a Monorepo: [Medium](https://medium.com/loftbr/creating-a-design-system-with-monorepo-bc18e055fb3c)