#3 Library of the Week: Turborepo

Dec 12, 2021

Is the new Vercel acquisition - Turborepo, the answer to the pain of maintaining a monorepo?


Monorepos are godsent when it comes to architecting a large codebase, yet we are still missing a solution for it that just works. "Complex problems require complex solutions" - you might have thought, but Turborepo is here to try to prove you wrong.

Unless you are building an isolated application that will never get expanded, you will stumble upon a common issue of sharing code between applications. You have three two options:

  1. You don't; you just copy and paste it instead.
  2. Build a collection of npm packages that you bring into each of your applications.
  3. Build your applications as a monorepo.

While we will cover the last option in this piece, up until now I was confident to say there is no clear winner between those two: it's simply a matter of use case. Jared Palmer's Turborepo, however, wants to take the lead in this competition.


Useful links:


Monorepo - from "mono" -> "one", and "repo" -> "repository", is a software building strategy that assumes keeping your entire codebase, which may consist of several applications, in one repository.

If the apps are truly separated (which is kind of against the very reason people go for this approach), the only issue to solve is how to update & deploy the app that has changed, not the entire codebase. However, what usually comes into play with monorepos is sharing packages between the applications.

A common scenario for using a monorepo would be something like this:

  • /packages/client - the client application [depends on: ui]
  • /packages/admin - admin panel [depends on: ui]
  • /packages/ui - the shared UI library

With those relations, you can only imagine how convoluted the process can get if not administered properly. Let's imagine:

What happens when you change the content of the client application and want to deploy it?

  1. It goes to fetch its dependencies, as well as the ui package it relies on.
  2. If it has everything it needs, it can build the app and deploy it.

What happens when you change the content of the ui package?

  1. It goes to fetch its dependencies, build itself.
  2. It triggers the build of both the admin and the client, as they rely on the ui package.

And what about the local development? Do you rebuild the ui package manually, every time you change something in it? With so many moving parts, it's easy to entangle some unnecessary steps in the process, losing time, and therefore money, as a result.

What is Library of the Week?

Library of the Week is a series published on my blog that encourages me to look for libraries that may have gone under your radar. The criteria I enforce are simple:

  • 👉The library must have less than 10,000 GitHub stars
  • 👉It must match my front-end ecosystem: React, Next.js, TypeScript, JavaScript

You can find the rest of the entries in the series here.

As you can see, the benefits of using a monorepo come at a heavy price. No wonder that across the years, there have been many libraries that tried to streamline the processes described above. Some made our lives significantly easier, such as Yarn workspaces, Lerna, Nx, or Rush, and while I don't have the experience with all of them, I feel like the ecosystem is still far from ideal. There is no natural choice for monorepo tooling. Or maybe there is now?


Just a few days ago, access to Turborepo was limited to paying customers, but luckily it was just required by one of my favorite companies - Vercel. Therefore, its features are available to the general public, and there are many. Instead of going through all of them, I want to focus on certain points from Turborepo's strategy that I find unique:

Pipelines

Turborepo relies heavily on its concept of pipelines, which is an explicit way of setting the relationships between the packages, instead of secretly sewing them across multiple config files or mysterious commands.

If you run npx create-turbo@latest, you can see that an example Next.js pipeline looks like this:

package.json

{
// ...
"turbo": {
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"dist/**",
".next/**"
]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}
}

What it says is the following, according to the documentation:

  1. build once its dependencies have run their build commands
  2. lint whenever
  3. turn off cache for dev command because of hot reloading

This approach has two advantages: readability (the dependencies are declared upfront), and predictability. Thanks to the latter, Turbo can perform a bunch of optimizations that make the entire build shorter.

Local and remote caching

By default, Turbo caches both the output of the build directory, as well as the logs that determine what's changed, making the process super fast.

The superpowers don't end here, though. Turbo is also capable of sharing cached artifacts between machines by storing them in the cloud, which I believe will only be amplified once the cooperation with Vercel fully kicks in. Right now, the service already caches your turbo builds by default.

Strong defaults and documentation

What has impressed me is the fact by running npx create-turbo@latest, you are presented with a boilerplate code that already eases some very common pains.

The structure of the example project is the following:

Files structure

As you can see, right away you have:

  • a shared UI library
  • shared configs
  • two apps relying on the shared packages

What is beyond the use case presented in the example app, you will find in the excellent documentation. Combine it with the ease of deployment to Vercel, and it's difficult to imagine the process of building a monorepo to be smoother than that.

No matter what the angle is, Turborepo always comes as polished and well thought out, which is no coincidence given its authors, and the publishing model that brought it to daylight. Given the fact that Remix was also born in similar circumstances (perfected behind closed, paid doors, and released once it's matured), one can wonder whether this path will not become more popular across other currently developed projects.


I don't feel like I've spent enough time with the biggest players on the monorepo market to give a verdict on which one is the most flexible and performant, but I can already say that Turborepo may be missing some features you know from its competitors, with versioning being the biggest one. The authors of the library are not trying to hide it, though, and who knows what the next months of Turborepo's development may bring us.

I am impressed enough to follow this technology closely and potentially even go for it when the time comes. And it will most certainly come because it's difficult to imagine building complex, interdependent front-end applications without monorepos.


These three will shock you 😲:


TwitterLinkedInGitHub