Monorepo management with tools like Lerna or Yarn Workspaces

Anton Ioffe - October 31st 2023 - 8 minutes read

Welcome to an in-depth exploration of monorepo management powered by Lerna and Yarn Workspaces. In this robust journey, you'll discover the benefits and potential pitfalls of employing a monorepo structure, the distinctive features these tools provide, and practical guidelines to create and resolve challenges in your monorepo. Get ready for an intriguing finale, where we delve into the untapped potential of these tools to maximize your monorepo's performance. Packed with real-world examples and actionable insights, it's time to demystify the world of monorepos, inviting you to move beyond the theoretical and forge your hands-on experience. Buckle up for an enlightening adventure in monorepo management.

Advantages and Use Cases of Monorepo Management with Lerna and Yarn Workspaces

Monorepos, essentially a single repository housing multiple packages or projects, have garnered popularity for their many advantages. Beyond the utility in reducing the need for npm link for interdependent projects, monorepos make it seamless to manage npm package dependencies in one place which is handy when using a dependency managing tool like Renovate. In scenarios where new features affect many packages, monorepos simplify the process as changes are made in one place instead of multiple related pull requests.

However, while monorepos ease interdependent development, they come with their own set of challenges. Managing issues and pull requests in one repository requires understanding of monorepo tools. Additionally, it can allow for potential dependency conflicts if multiple versions of a dependency exist in different packages, increase memory usage affecting performance in version control systems, and lead to high chances of merge conflicts. The initial setup of a monorepo can also be quite time-consuming. These hurdles underscore the need for effectively managing monorepos with tools such as Lerna and Yarn Workspaces.

Lerna optimizes monorepo management by facilitating a shared workflow around managing multi-package repositories with Git and npm. By combining Lerna with Yarn Workspaces, the workflow is further optimized at the package manager level, offering a more intuitive way to manage monorepos. In practical terms, projects using Yarn Workspaces alongside Lerna allow for all project dependencies to be installed at once, rather than on a per-package basis, with dependencies being linked in a more efficient manner.

To get Lerna and Yarn Workspaces working optimally together, we set the "npmClient" value to "yarn" in lerna.json. Also, by setting the "useWorkspaces" parameter to true, we inform Lerna to use Yarn Workspaces. On the other hand, the implementation of Yarn Workspaces involves setting the private and workspaces fields in the package.json in the root directory, where the workspaces field specifies the directory in the repo to house the packages. This approach creates one yarn.lock file in the root directory and uses it to manage all dependencies across multiple packages, instead of creating multiple ones, thus minimizing diffs when updating dependencies.

These advantages and practical use cases highlight how Lerna and Yarn Workspaces can facilitate the management of monorepos, ensuring an efficient workflow even as projects expand and become more complex.

The Feature Pack of Lerna and Yarn Workspaces

The two tools, Lerna and Yarn Workspaces, bring to the table a vast array of features ideal for monorepo management. The ability of Yarn Workspaces to install dependencies from multiple package.json files with a single command, utilizing a feature known as 'Workspaces', drastically reduces dependency management complexity. Furthermore, it creates a singular yarn.lock file for handling all dependencies across packages, resulting in minimal diffs when updating dependencies.

Lerna provides support to multiple-package projects hosted within a single repository by easing the management of npm links. It streamlines the workflow by analyzing each package and automates the process of creating the necessary npm links. Its efficient execution of tasks across different packages simplifies versioning and publishing. Interestingly, Lerna can be paired with Yarn Workspaces by setting the npmClient property in lerna.json to "yarn", thereby enhancing the tool's functionality.

When configuring Yarn Workspaces for use with Lerna, the private field in package.json has to be set to true. For the workspaces field, the directories containing your packages should be listed. For instance, if the packages are placed under a directory named 'examples', the same should be mentioned as a workspace. With this setup, a command as simple as 'yarn install' would install the dependencies of all packages located in the specified workspace directories.

Lerna and Yarn Workspaces combined present an optimized environment for monorepo management. With Yarn Workspaces handling package level management and Lerna providing high level optimizations, a more streamlined workflow is achieved, thereby enhancing the efficiency of managing a monorepo.

Constructing a Monorepo with Lerna and Yarn Workspaces

Setting up a monorepo entails creating a top-level project structure, and one common practice often adopted is creating a packages directory. This directory will house all the packages within our monorepo. Your structure might look something like this: mono-repo/ package.json packages/ package-a/ package.json package-b/ package.json.

To solve interactive work in such a workspace, we introduce Lerna and Yarn Workspaces. First, let's talk about setting up Lerna. Begin by installing Lerna as a dev dependency by running npx lerna init. This command initiates a new lerna project, creates a lerna.json configuration file, and generates a packages folder. Your folder hierarchy may now look like this: mono-repo ── packages ── README.MD ── lerna.json ── package.json.

Defining a smooth and efficient workflow is the primary objective for setting up a monorepo with Lerna and Yarn Workspaces. In your lerna.json file, specify your npmClient to 'yarn' and set useWorkspaces to true:

{
 "packages": ["packages/*"],
 "npmClient": "yarn",
 "useWorkspaces": true,
 "npmClientArgs": ["--no-lockfile"],
 "version": "independent"
}

This configures Lerna to collaborate with Yarn Workspaces and paves the way for an effective workspace management.

Now, let's enable Yarn Workspaces in our monorepo. In the root package.json, add "private": true and "workspaces": ["packages/*"] to enable Yarn Workspaces. This means all folders within packages directory are added as Yarn Workspaces. Now when you run yarn install, dependencies of all the packages specified in workspaces are installed. Just to be clear, the root package.json might look something like this:

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

That completes the basic setup of a monorepo using Lerna and Yarn Workspaces.

Resolving Monorepo Challenges with Lerna and Yarn Workspaces

A sound monorepo management necessitates overcoming certain common challenges. Let's reflect on these issues and how we can resolve them with tools like Lerna and Yarn Workspaces.

One issue relates to local devDependencies. If your packages need to use binaries within their npm scripts, these binaries have to be declared locally within devDependencies. For example, if a package makes use of the 'jest' command in its npm scripts, 'jest' should be stated as a devDependency in the package's package.json file. Failing to do so will require the use of cumbersome relative paths ('../../') in your npm scripts.

// package.json in the local package directory
{
...
"devDependencies": {
    "jest": "^24.8.0",
    ...
}
...
}

Next, scripting within a monorepo can be challenging, especially when you have scripts that run in watch mode. For instance, if you wish to use a jest --watch script, it should ideally run both from the root of the monorepo, and from directly within each individual package. Lerna is instrumental here. With Lerna, you can define a script on the root level and run the command lerna run --parallel commandName, which executes the stated script for each package in the monorepo, concurrently.

// package.json in the root directory
{
...
"scripts": {
    "test": "lerna run --parallel test",
    ...
}
...
}

Lastly, if you're using private npm repositories, Lerna encounters issues because it's not designed to publish private npm packages. An optimal workaround is to publish your npm packages programmatically. You may choose to invoke npm client commands from scripts in your package.json file, or create a script file that utilizes npm APIs to publish your packages.

// package.json in the root directory
{
...
"scripts": {
    "publish-packages": "node scripts/publish.js",
    ...
}
...
}

Deploying Lerna and Yarn Workspaces the right way, as demonstrated, addresses common hiccup and challenges in monorepo management. This elevates the effectiveness, readability, and maintainability of your codebase, aiding in modular and scalable projects.

Just a point to ponder - how you can further optimize your monorepo management strategies with these tools?

Enhancing Monorepo Performance with Advanced Techniques in Lerna and Yarn Workspaces

The core of advanced monorepo management lies in uncovering techniques that go beyond just managing 'multi-package repositories'. One such technique prevalent in Lerna involves 'selective package running'. Through lerna run --scope <package> you can selectively run scripts for a particular package. This allows you to perform focussed regression testing or merely run a specific part of your application reducing the overhead from unnecessary procedures. However, remember that Lerna can sometimes misinterpret the relatedness of packages, leading to some missed dependencies or improper script runs. Ensuring explicit package.json dependencies can solve this, preserving the accuracy in package referencing.

Up next, we delve into the realm of Yarn Workspaces. One often overlooked advantage of yarn workspaces is its 'single version policy'. This unique feature ensures there's only one version for each package inside the monorepo. It prevents duplicate dependencies, efficiently pruning the dependency tree in your disk space and consequently speeding up installation time. However, this convenience can lead to incompatibility issues when dealing with multiple versions of certain packages. The way around this challenge is to leverage resolutions field in the root package.json, which helps to enforce the package version across all workspaces.

In regard to enhancing performances, Lerna's use of 'independent versioning' should be noted. This approach, indicated by lerna.json's version property set to independent, allows you to control package versions independently. It gives you the flexibility to release updates only for packages that have changed and their respective dependencies. This enables granular updates, potentially reducing system complexity and save computational resources. However, managing independent versions can be difficult for a large number of packages, as each package has its own lifecycle.

Finally, let's highlight one of the most powerful features of Yarn Workspaces - 'Workspaces specific commands'. Using yarn workspace <workspace_name> add <dependency> you can add a dependency to a specific workspace. It provides a clear, individualized scope and bolsters task automation, reducing time consumption. Nevertheless, this feature comes with a word of caution. Misusing it can lead to bloated node_modules, so it's imperative to evaluate the need for a dependency at a workspace level versus at the root level.

Reflect on these advanced practices and imagine how they can improve your monorepo management technique. Each of these methods comes with its own set of trade-offs but recognizing and handling them properly will bring substantial benefit to your project's performance.

Summary

In this article, we explore the benefits and challenges of monorepo management using tools like Lerna and Yarn Workspaces. We learn how these tools simplify the management of dependencies in a monorepo, streamline the workflow, and optimize performance. The article provides practical guidelines for setting up and resolving challenges in a monorepo, as well as advanced techniques for enhancing performance. As a challenging task, readers are encouraged to explore how they can further optimize their monorepo management strategies using these tools, thinking about innovative ways to streamline their workflow and improve performance.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers