
In revision control systems, a monorepo (a syllabic abbreviation of a monolithic repository) is a software development strategy where code for many projects are stored in the same repository. Splitting up large codebases into separate independently versioned packages is extremely useful for code sharing. However, making changes across many repositories is messy and difficult to track, and testing across repositories gets complicated really fast. Several companies have embraced this strategy like Google, Facebook, Microsoft, Uber, Airbnb and Twitter.
Why use a Monorepo?
- Easily refactor global features with atomic commits. Instead of doing a pull request for each repo, figuring out in which order to build your changes, you just need to make an atomic pull request which will contain all commits related to the feature that you are working against.
- Simplified package publishing. If you plan to implement a new feature inside a package that is dependent on another package with shared code, you can do it with a single command. It is a function that needs some additional configurations, which will be later discussed in a tooling review part of this article. Currently, there is a rich selection of tools, including Lerna and Yarn Workspaces.
- Simplified dependency management — In a multiple repository environment where multiple projects depend on a third-party dependency, that dependency might be downloaded or built multiple times. In a monorepo the build can be easily optimized, as referenced dependencies all exist in the same codebase.
- Re-use code with shared packages while still keeping them isolated. Monorepo allows you to reuse your packages from other packages while keeping them isolated from one another. You can use a reference to the remote package and consume them via a single entry point. To use the local version, you are able to use local symlinks. This feature can be implemented via bash scripts or by introducing some additional tools like Lerna or Yarn.
With Lerna, we now manage a single repository for all of our packages, with a directory structure that looks like this:
mylerna_repo/
- node_modules
- packages
- client
package.json
- server
package.json
- docs
package.json
lerna.json
package.json
Tool Review
The set of tools for managing monorepos is constantly growing, and currently, it’s really easy to get lost in all of the variety of building systems for monorepos. You can always be aware of the popular solutions by using this repo. But for now, let’s get a quick look at the tools that are heavily used nowadays with JavaScript:
- Yarn is a JavaScript dependency management tool that supports monorepos through workspaces no-hoist.
- Lerna is a tool for managing JavaScript projects with multiple packages, built on Yarn.
Yarn
Yarn is a dependency manager for NPM packages, which was not initially built to support monorepos. But in version 1.0, Yarn developers released a feature called Workspaces. At release time, it wasn’t that stable, but after a while, it became usable for production projects.
Workspace is basically a package, which has its own package.json and can have some specific build rules (for example, a separate tsconfig.json if you use TypeScript in your projects.). You actually can somehow manage without Yarn Workspaces using bash and have the exact same setup, but this tool helps to ease the process of installation and updating dependencies per package.
At a glance, Yarn with its workspaces provides the following useful features:
- Single
node_modules
folder in the root for all packages. For example, if you havepackages/package_a
andpackages/package_b
—with their ownpackage.json
—all dependencies will be installed only in the root. That is one of the differences between how Yarn and Lerna work. - Dependency symlinking to allow local package development.
- Single lockfile for all dependencies.
- Focused dependency update in case if you want to re-install dependencies for only one package. This can be done using the
-focus
flag. - Integration with Lerna. You can easily make Yarn handle all the installation/symlinking and let Lerna take care of publishing and version control. This is the most popular setup so far since it requires less effort and is easy to work with.
Lerna
This tool really helps while dealing with semantic versions, setting up building workflow, pushing your packages, etc. The main idea behind Lerna is that your project has a packages folder, which contains all of your isolated code parts. And besides packages, you have the main app, which for example can live in the src folder. Almost all operations in Lerna work via a simple rule — you iterate through all of your packages, and do some actions over them, e.g., increase package version, update dependency of all packages, build all packages, etc.
With Lerna, you have two options on how to use your packages:
- Without pushing them to remote (NPM)
- Pushing your packages to remote
While using the first approach, you are able to use local references for your packages and basically don’t really care about symlinks to resolve them.
But if you are using the second approach, you are forced to import your packages from remote. (e.g., import { something } from @name/packagename;
), which means that you will always get the remote version of your package. For local development, you will have to create symlinks in the root of your folder to make the bundler resolve local packages instead of using those that are inside your node_modules/
. That’s why, before launching Webpack or your favourite bundler, you will have to launch lerna bootstrap
, which will automatically link all packages.
Conclusion
Going “monorepo” today usually means turning a repository into a multi-package repository from which multiple packages can be published. This repository is part of a multi-repo architecture and lives in its ecosystem.
Tools like Bit (which was built for code-sharing in a multi-repo codebase), Lerna and Yarn workspaces help to optimize this workflow, and breed code-sharing for faster development and simplified maintenance.
Choosing the right tooling means understanding what are you going to build, why are you building it, and how do you expect other people to use it. Answering these questions can help you make good choices from the get-go, which will make your life much easier down the road.
Don’t forget: sharing code is about tools and technology, but also about people and communication. The right tools can help you share and communicate, but won’t replace team-works and collaboration.