DEV Community

Honeybadger Staff for Honeybadger

Posted on • Originally published at honeybadger.io

Monorepo Javascript Projects with Yarn Workspaces and Lerna

This article was originally written by Saiharsha Balasubramaniam on the Honeybadger Developer Blog.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/*"]
}
Enter fullscreen mode Exit fullscreen mode

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/*"]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

yarn storybook
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
));
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)