11 Great Tools for a Monorepo in 2021

Jonathan Saring on 2020-11-30

Probably the best tools to develop and build your monorepo.

These days, many tools can run “npm install” and “npm run build” in 20 different folders. But, not all tools can facilitate a proper monorepo.

Facilitating a proper monorepo development means solving challenges like operating the test and build processes for decoupled modules, being able to independently publish modules from the project, and managing the partial impact of changes on every affected dependant module in the project.

The list of challenges goes on, to include even “trivial” things like how you manage issues and PRs, which can get hard as you scale development.

Note that a monorepo is NOT a monolith application(!) — it is not built or deployed all at once. It is a group of applications developed separately.

In this roundup, I’ve gathered some of the best tools in the world to construct a “monorepo”, where you can build multiple modules inside a single project, with a decent developer experience that scales.

The list isn’t ranked and aims to outline the strengths of each tool based on its own merits. I hope this can help you save time and find the right tool.

Feel free to comment below and share your own insights. Cheers!

1. Yarn Workspaces

Yarn Workspaces aims to make working with monorepos easy, solving one of the main use cases for yarn link in a more declarative way. Your dependencies can be linked together, which means that your workspaces can depend on one another while always using the most up-to-date code available. This is also a better mechanism than yarn link since it only affects your workspace tree rather than your whole system.

Workspaces helps with a few issues, making it a great monorepo setup:

As such, Yarn workspaces makes a very powerful combo with pretty much any tool on the list, and especially tools like Bit, Nx, and Lerna, acting as a lower-level layer of your monorepo management abstraction.

However, you can also publish directly with workspaces. When a workspace is packed into an archive, it dynamically replaces any workspace: dependency with a package version, so you can publish the resulting packages to the remote registry without having to run intermediary steps — consumers will be able to use published workspaces as any other package. Cool!

2. Bit

Bit is a next-generation tool for building modular projects. It is a new and exciting approach to monorepos where modules managed by the same project (the same Bit workspace) are actually distributed across different scopes regardless of repositories.

Bit lets you split the development of modules in a fully decoupled way, and enjoy a simple, holistic dev experience for orchestrating everything.

The dependency graph of a simple React Native Application in Bit’s workspace UI

Using bit you can decouple components in your project so that each of them is independently developed, built, tested, and published. Each component is developed and built using special environments, which are extensible and reusable so that you can quickly customize and use them again.

Bit’s workspace manages all the relationships between components in the project. When you make changes to any component, Bit builds and tests it in isolation, and propagates the change up the dependency graph.

Components can be bulk published, as independent packages, to NPM and/or to the bit.dev platform for collaboration, consumption, and documentation.

Customizable, compsable component docs in Bit

Bit’s UI helps you view the development of your monorepo. As you code, and each component is documented, tested, built etc, you can visually see what’s happening with live feedback and hot-reloading.

Bit provides decoupled dev environments — Reusable and customizable modules that configure and “bundles” together different services needed throughout the life-cycle of an independent component such as compiling, bundling, testing, linting, documenting, and more.

Bit’s workspace decouples component development in a simple and holistic way

Mastery of the component graph — Bit defines, manages, and helps you leverage the relationship between all components in your project.

Graph-driven builds — When you make a change to a component, Bit automatically detects which other components depend on it, and “knows” to build only the affected graph of dependent components.

A “graph-driven build” also means that in case a component gets tagged with a new release version (before being exported to Bit’s cloud), Bit will not only run the build on every affected component but will also make sure to tag them with a new release version (with a bumped version number).

Isolated tests and builds — Each component is built and tested in isolation, literally outside of the project, so you can see exactly how changes impact.

Component build pipelines — You can pipe build jobs in a reusable pipeline that can be applied to all components in a project or in all of your projects.

Bulk publishing— Every component developed in a Bit monorepo is ready to be published as a standalone package. Bit strips aways all the overhead of configuring each component’s ‘package.json’ and other setup files. All you have to do is run ‘bit tag’ so that Bit will auto-tag all changed components with a version bump (supporting semver rules), and then bulk publishes changes.

Reusable docs templates— Every component is documented using reusable and customizable templates, and Bit automates much of the work for you. Working with MDX? Maybe also add some visual examples? No problem.

Compositions rendered in isolation — Every component is rendered in full isolation, literally outside of the project, and the rendered visuals (which hot-reload as you code) become a part of each component’s documentation.

3. NX

NX is an advanced set of extensible dev tools for monorepos, with a strong emphasis on modern full-stack web technologies.

Empty NX monorepo

NX aims to provide a holistic dev experience via CLI (with editor plugins), and capabilities for controlled code sharing, and consistent code generation. It also provides incremental builds, so it doesn’t rebuild and retest everything on every commit you make, resulting in faster build times.

NX’s command execution allows for consistent commands to test, serve, build, and lint each project. It uses a distributed computation cache so if someone has already built or tested similar code, Nx will speed up the command for everyone else instead of rebuilding or retesting the code from scratch.

With Nx you can use your preferred framework, integrate with modern tools you’re probably already using. For example, NX lets you use out of the box integration with Cypress, Jest, Typescript, Prettier, and other tools.

The NX team also provides NX cloud, with smart computation memoization in the cloud and faster builds to help your team working with NX deliver faster.

Build only what is changed with NX, cut your build times

4. Rush

Rush is a powerful monorepo infrastructure by Microsoft + Open Source. It aims to help you, well, build and publish many packages in one repository.

A landing page and some components, two projects, one repo

Some of the key features of rush include a single NPM install (also works with Yarn and pnpm) so you can install all dependencies for all projects into a common folder, using isolated symlinks to reconstruct an accurate “node_modules” folder for each project.

This also helps to ensure there are no phantom dependencies, so you won’t accidentally import a library that was missing from your package.json, and there are no dependency duplications where you find 10 copies of lib in your node_modules.

Rush interactive CLI is nice

Automatic local linking means all your projects are automatically symlinked to each other, and when you make a change, you can see the downstream effects without publishing anything, and without any npm link headaches.

Rush’s unique installation strategy produces a single shrinkwrap/lock file for all your projects that installs fast. Rush detects your dependency graph and builds your projects in the right order, so if two packages don’t directly depend on each other, Rush parallelizes their build as separate processes.

If you only plan to work with a few projects from your repo, Rush provides subset and incremental builds so rush rebuild --to <project> does a clean build of just your upstream dependencies. After you make changes, rush rebuild --from <project> does a clean build of only the affected downstream projects. And rush build delivers a powerful cross-project incremental build. Rush even handles cyclic dependencies, by separating versions for projects.

When you want to release, Rush supports bulk publishing, so it detects which packages have changes, automatically bumps all the relevant version numbers, and run npm publish in each folder.

Rush also helps to implement and enforce development policies. For example, when a PR is created, you can require developers to provide a major/minor/patch log entry for the affected projects, which will later be aggregated into a changelog file upon publishing. It also helps you enforce stuff like pre-publish reviews, specific dependency versions, and so on.

5. Lerna

Lerna (named after the home of Hydra, the multi-headed beast) is a “tool for managing JavaScript projects with multiple packages”.

Lerna was created to solve the multi-package for Babel, to optimize the workflow of managing multi-package repositories with git and npm. It’s essentially tools and scripts to effectively manage and publish many independently versioned packages in a single Git repository.

my-lerna-repo/
  package.json
  packages/
    package-1/
      package.json
    package-2/
      package.json

The two primary commands in Lerna are lerna bootstrap and lerna publish.bootstrap will link dependencies in the repo together. publish will help publish any updated packages.

You can manage your project using one of two modes: Fixed or Independent.

Fixed mode Lerna projects operate on a single version line. The version is kept in the lerna.json file at the root of your project under the version key. When you run lerna publish, if a module has been updated since the last time a release was made, it will be updated to the new version you're releasing. This is the mode that Babel is currently using.

A Lerna with Yarn Workspaces example; Source noteworthy

Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it’s a patch, minor, major or custom change. Independent mode allows you to more specifically update versions for each package and makes sense for a group of them.

The ‘lerna.json’ file is a list of globs that match directories containing a package.json, which is how lerna recognizes "leaf" packages (vs managing the dev dependencies and scripts for the entire repo). Example:

{
  "version": "1.1.3",
  "npmClient": "npm",
  "command": {
    "publish": {
      "ignoreChanges": ["ignored-file", "*.md"],
      "message": "chore(release): publish",
      "registry": "https://npm.pkg.github.com"
    },
    "bootstrap": {
      "ignore": "component-*",
      "npmClientArgs": ["--no-package-lock"]
    }
  },
  "packages": ["packages/*"]
}

Even if you’re not intending to publish to NPM, Lerna can still be helpful in managing versioning and common development tasks in a monorepo.

6. Bazel Build System (Google)

Google introduced the Bazel build system. It is an open-source build and test tool similar to Make, Maven, and Gradle that uses a human-readable, high-level build language. Bazel supports projects in multiple languages and builds outputs for multiple platforms. It supports large codebases in a big monorepo or across multiple repositories, and large numbers of users.

Uber Developers uses Bazel to build their Go monorepo. Uber writes most of its back-end services and libraries in Go, which in 2018 were all grouped into a large Go monorepo which now have over 100,000 files. Bazel allowed this project to scale, cutting build times, and supporting its growth.

Here’s a nice small open-source project with Bazel as a demo:

thundergolfer/example-bazel-monorepo Note: Currently supporting the latest Bazel version as at mid July 2020, 3.3.0 Example Bazel-ified monorepo, supporting…github.com

Bazel is designed to work at scale and supports incremental hermetic builds across a distributed infrastructure, which is necessary for a large codebase. With Bazel’s remote cache, build servers can also share their build artifacts. Bazel caches all previously done work and tracks changes to both file content and build commands. A package is built and tested only when something has changed either in the package or its dependencies.

Bazel runs on Linux, macOS, and Windows. Bazel can build binaries and deployable packages for multiple platforms, including desktop, server, and mobile, from the same project. Many languages are supported, and you can extend Bazel to support any other language or framework.

7. Buck Build System (Facebook)

Buck is a build system that encourages the creation of small, reusable modules consisting of code and resources, and supports a variety of languages on different platforms.

It is developed and used by Facebook, and has gained its fame by serving as the official build system of the FB monolith and thanks to being used by teams like Uber Developers to greatly reduce build times. The team at AirbnbEng marked 50% faster builds and 30% smaller apps.

Uber got better build results with buck

Buck is designed to build a monorepo, and support for the monorepo design motivated Buck’s support for cells and projects.

It is Facebook’s experience that maintaining all dependencies in the same repository makes it easier to ensure that all developers have the correct version of the code and simplifies the process of making atomic commits.

Buck is commonly used for Android and iOS development. You can find more use-cases for buck here in this showcase page.

Buck can help you and your team in many ways:

Like other tools on the list, Buck builds independent artifacts in parallel to take advantage of multiple machine cores. It reduces incremental build times by keeping track of unchanged modules so that the minimal set of modules is rebuilt. And, Buck only uses the declared inputs, which means everybody gets the same results.

More cool features include buck query, which helps you better understand your dependencies and what is required to build your product. And buck project helps your IDE better “understand” the project your building.

8. Pants Build System (Twitter)

In 2014, Twitter introduced its monorepo build system called Pants. Today, on v2, Pants aims to be a fast, scalable build system for growing codebases. It’s currently focused on Python, with support for other languages coming soon.

Pants uses a fine-grained workflow, and isolates each work unit from side effects, so it can utilize all available cores. Some of Pant’s best features include explicit dependency modeling, fine-grained invalidation, shared result caching, concurrent execution, remote execution, and extensibility and customizability via a plugin API.

The Pants engine is written in Rust, for performance. The build rules are written in typed Python 3, for familiarity and simplicity. The engine is designed so that fine-grained invalidation, concurrency, hermeticity, caching, and remote execution happen naturally, without rule authors' intervention.

Not using Python? Move on, nothing to see here. But if you do, you might want to take Pants for a test run before setting up your build system.

pantsbuild/pants Pants is a scalable build system for monorepos: codebases containing multiple projects, often using multiple…github.com

9. Please Build System

Please is a cross-language build system with an emphasis on high performance, portability, extensibility and correctness.

Please makes sure build steps are executed in their own hermetic environment, with access to only files and env variables that they have been given access to. Incremental builds means it only builds what it needs to, and it also provides task parallelism, and distributed caching, for a reliable and performant build system at scale.

Please also aims to focus on dev experience, so you can enjoy a famlar CLI and define aliases for common tasks leveraging auto-completions. Written in Go, Please provides all this user experience with no runtime dependency. And, there’s no single large workspace file with too much configs to handle.

It as an intriguing option to explore as your monorepo’s build system.

thought-machine/please Please is a cross-language build system with an emphasis on high performance, extensibility and reproducibility. It…github.com

10. Oao

Oao isn’t the most mature, rich, or easily usable tool on the list, but it’s interesting none the less. It is a Yarn-based, opinionated monorepo management tool thatp provide monorepo features like installing all dependencies, adding/removing/upgrading sub-package dependencies, validating version numbers, determining updated sub-packages, publishing everything at once, updating the changelog, etc.

Oao lets you run a command or package.json script on all sub-packages, serially or in parallel, optionally following the inverse dependency tree. And, it supports yarn workspaces, optimising the monorepo dependency tree as a whole and simplifying bootstrap as well as dependency add/upgrade/remove.

Support for non-monorepo publishing: benefit from oao’s pre-publish checks, tagging, version selection, changelog updates, etc. also in your single-package, non-monorepos. Note that Oao uses a synchronized versioning scheme so a master version is configured in the root-level package.json, and sub-packages will be in sync with that version . You can try it out here.

guigrpa/oao A Yarn-based, opinionated monorepo management tool. Works with yarn, hence (relatively) fast!. Simple to use and extend…github.com

11. Bolt

Boltpkg aims to be a a “ Super-powered JavaScript project management tool”.

Bolt implements the idea of workspaces on top of Yarn. Bolt CLI is largely a replacement of the Yarn CLI. You can use it on any Yarn project.

As we know, workspaces are nested within a larger project/repo. Each workspace can have its own dependencies with its own code and scripts. Workspaces can also be grouped into sub-directories for organization.

Using Bolt, you can install the dependencies for all of these packages at once (and you can do it really really fast). And, when you specify a dependency from one workspace to another, it will get linked to the source. This way, when you go to test your code, all your changes get tested together.

What about Git Submodules / Git Subtrees / Git Slave?

4 Git Submodules Alternatives You Should Know Want to develop and share smaller modules between repositories? Here are some useful options.codeburst.io

Honorable Mentions ✨

Watch out, some of these are no longer actively maintained. ✋

simplesurance/baur baur is a build management tool for Git based monolithic repositories. Developers specify configuration in a TOML file…github.com

FormidableLabs/builder An npm-based task runner. Contribute to FormidableLabs/builder development by creating an account on GitHub.github.com

FormidableLabs/lank Working on multiple repositories that depend on each other can be a real pain. npm link-ing often has limitations…github.com

mbtproject/mbt The most flexible build orchestration tool for monorepo Documentation | Twitter Differential Builds Content Based…github.com

Learn More

Building a React Component Library — The Right Way Create an ultra-modular component library: Scalable, Maintainable, and with a Blazing-fast setup.blog.bitsrc.io

Building a React Design System for Adoption and Scale Achieve DS scale and adoption via independent components and a composable architecture — with examples.blog.bitsrc.io

How We Build Micro Frontends Building micro-frontends to speed up and scale our web development process.blog.bitsrc.io

Independent Components: The Web’s New Building Blocks Why everything you know about Microservices, Micro Frontends, Monorepos, and even plain old component libraries, is…blog.bitsrc.io