Should you go for a monorepo or a poly-repo? Should you use a monorepo solution better suited for managing integrated applications or one better suited for managing libraries and publishing packages?
This blog will explain how Bit can be used to implement any architecture and transform “fatal” decisions that seem too hard to change into decisions that are easy to make and change.
We’ll use the "Bits and Pieces" article submission form as an example project to demonstrate the different approaches.
The article submission form app for guest writers
The project is a simple React app that allows users to submit articles for publication.
The app is composed of components from a curated design system. The form data is sent to a serverless Lambda function that saves the article to a database.
In a poly-repo approach, we might have a repository for each application, the client and the API, and a repository for the design system. In a monorepo, a single repository will maintain all our libs and apps.
In this article, we’ll go from a monorepo handled by Bit:
To a fully distributed codebase that consists of independent Bit components:
Bit in repositories
When using Bit in a repository, we decide to treat the repository as the source of truth for all our Bit components.
In this case, we enjoy many of the benefits Bit offers while minimizing the effect Bit has on our development workflow and existing tools.
We can collaborate using our existing Git workflow and tools to build, test, and deploy our applications and services.
When used as a monorepo solution, Bit provides intelligent and efficient dependency management, insight into our codebase, efficient incremental builds, and an easy way to publish packages.
For instance, see this repository that contains the components used in the article submission form. For simplicity's sake, this example uses only Bit, but remember that Bit can be integrated into any existing repository and used alongside any other monorepo tool.
Try out this monorepo example by cloning the repository:
git clone https://github.com/bitdev-community/bnp-article-submission
cd bnp-article-submission
bit install
Run Bit’s workspace UI to get a visual overview of the components in the project:
bit start
Browse around the components and see how they are connected:
Going from monorepo to multi-repo
As you can see, unlike many traditional monorepo solutions, there is no distinction between “apps” and “libs” in this repository. All the components are managed by Bit and are treated as “packages” with the potential to run independently and be deployed as apps.
The project is composed of components of all levels of complexity. Some components are used in multiple places, and others are only used in one place. All of them are published as packages.
One of the things that enables this is that all components are consumed via an absolute package name rather than a relative path.
For example, the article-submission-form
component consumes this schema validator using its package name and not using its relative path in the repo:
/**
* @filename: article-submission-form.tsx
**/
import {
ArticleSubmission,
articleSubmissionScheme,
} from '@bits-and-pieces/article-submission.entities.article-submission-scheme';
This allows you to move components around the repository without breaking the code that consumes them. More importantly, it allows you to move components between repositories without breaking the code that consumes them.
For example, say that at a certain point, the publication decides to refactor the project and move some parts of it to separate repositories:
The design team will handle the 'design system’ components in a separate repository.
The frontend dev team will handle the ‘article submission’ client side in a separate repository.
The backend dev team will handle the API for the ‘article submission’ in a separate repository.
The process of moving components between repositories is very simple. All you need to do is export the components you want to move and then import them into the new repository.
For example, the following commands create a new version of the article-submission-scheme component, export it to a remote hosting (“remote scope”), and stops maintaining it in the current repository (i.e., install it as a regular package instead of symlinking to its source files in the project):
bit tag article-submission-scheme
bit export article-submission-scheme --eject
From the new repository, you can import the component using the following command (here, we assume the component was exported to the ‘article-submission’ scope maintained by the ‘bits-and-pieces’ organization):
cd article-submission-client
bit import bits-and-pieces.article-submission/entities/article-submission-scheme
The component can now be developed and updated in its new repository.
What about the code that consumes the component in the previous repository?
It will continue to work as before because it still consumes the component via its absolute package name.
What about the component’s development environment? build setup? tests?
They are all part of a reusable development environment configured on the component itself (the env will also be exported to a remote hosting or “scope” if it’s not available there already).
Bit with no repository: entirely distributed
Exporting components to a remote hosting (“scope”) and importing them into a new repository is not limited to just a number of permanent repositories.
It can be used to collaborate on components without having to maintain a repository at all. In this case, the source of truth for the components is the remote hosting (the “scope”).
Permissions to modify or use components are managed only in the remote hosting, the ‘remote scopes’.
Importing only the components you need into “disposable” Bit workspaces, modifying them, and exporting them back keeps your workspace simple, clean, and understandable.
This distributed form of collaboration significantly improves your dev experience and makes onboarding new teammates and scaling up, in any sense of the word, smoother and more manageable.
What about the CI?
Bit uses Ripple CI as the default CI. Ripple CI is a CI system designed for Bit components.
Ripple with Bit enables efficient builds that run only on modified components and their dependencies (the affected components). Ripple also builds sibling components in parallel for faster builds.
Most importantly, since every Bit component is independent and autonomous, its build steps are determined by it (the component) rather than one central (external) place.
As mentioned at the beginning of this article, “apps” are not much different than “libs.” Both are Bit components hosted on remote scopes and published as packages.
That said, “apps” or app components will have additional build tasks responsible for generating deployable assets and then deploying them to the relevant platform.
One such ‘app component’ is the article-submission-app
. It is a Bit component, much like other components. It can even be installed as a package (build-time micro frontends, anyone?).
Top comments (5)
Each approach has its pros and cons that's for sure, every company needs to analyze what's best for them.
As a rule of thumb, a software factory would probably benefit more from a monorepo whereas a product company with well-defined product lines will probably do better with a poly-repo approach.
Regarding the approach mentioned in the post I have a couple of concerns initially but I need to read it carefully later on (I'm rushing a bit now 😅)
Sounds pretty cool. I'm usually not a huge fan of absolute imports tho because VS Code doesn't automatically refactor them in case if I move the module being imported. Also I wonder if by using this system I'm being locked in into using Ripple CI? With NX, for example, I'm free to install it anywhere: local or on-premises and it will still do smart incremental builds. Another concern is type definitions in typescript packages. If I use separate packages - I will have to rebuild I order for d.ts files to get generated, so that I can use newly added export in another package, which can become annoying if you're working on multiple packages at once...
Anyway, looks like a cool concept, thanks for sharing 👍
Ripple is the default CI for Bit (and the recommended one) but you choose to run the components' build pipeline on any CI platform (you can read more about it in the docs.
Regarding the d.ts files - when a component is maintained locally, Bit ensures its corresponding package uses its source files for type resolution so that you don't have to re-generate the declaration files on every change.
Love the idea of autonomous modular repo-ish entities
I wasn't a fan of Monorepo's approach, though I have had to live with it in the past. This is super interesting since we can get the same benefits of a Monorepo in Polyrepos using Bit Components.