Monorepo Javascript Projects with Yarn Workspaces and Lerna

  • A photo of Saiharsha Balasubramaniam By Saiharsha Balasubramaniam
  • on Jan 17, 2022
 

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 is used to optimize the management of monorepos. We'll use this tool to manage shared dependencies.
  • Yarn Workspaces is used to optimize and link different packages together.
  • Storybook 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.

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.
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.

{
  "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.

Let us create a package.json for our project:

{
  "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:

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.

{
  "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.

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.

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.

npx sb init

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

yarn storybook

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

Storybook Interface 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.

{
  "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.

// 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.

// 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 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.

Further Reading

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

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Saiharsha Balasubramaniam

    Saiharsha Balasubramaniam is a senior undergraduate, majoring in Computer Science at Amrita Vishwa Vidyapeetham University, India. He is also a passionate software developer and an avid researcher. He designs and develops websites, and loves blockchain technology. Currently, he is an SDE Intern at Flipkart and a Microsoft Learn Student Ambassador.

    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required