Just a few days ago we open-sourced the React components we use to build the bit.dev platform.
A quick look will show you that we actually opened-sourced not 1 GitHub library, but 2 different codebases. In fact, independent components with different versions are mixed from both codebases to create every page.
The first codebase is base-ui - a generic set of infrastructure-UI React components used across the bit.dev website.
The second codebase is Evangelist - the concrete React component design system we use to build the bit.dev marketing website.
Separating these components into two different front-end codebases (or "component micro-frontends" if you will) is part of how we manage reusable React components in a scalable and collaborative way - to speed our development and ensure a consistent UI across apps and screens.
In this post I'll show you how we manage our reusable components in React, from development to integration in our applications.
I'll show you everything from A-Z:
1. Different component codebases.
2. Exposing components for reuse and integration.
3. Composing pages applications.
4. Collaboration and updates.
Let's get to it.
We build our components in different codebases according to the different scopes and teams they belong to. For example, the concrete marketing components (pages, sections, content) is maintained in a different codebase than the infra-ui components (layout, atoms, themes).
Is this considered micro-frontends? Depends on who you ask. What I can tell you is that we are able (thanks to Bit which I'll show you in a sec) to build and maintain these sets of components in decoupled codebases and expose components to use elsewhere, which is similar to micro-services.
Take a look at the base-ui repository on GitHub where we build some basic UI components in React like Card, Image etc. This codebase is much simpler than that of an entire application, making it easy to develop.
And then there's also the "Evangelist" GitHub repository where we build concrete components (sections, content) to use in our marketing pages.
This kind of flexibility also provides us with the freedom to split team ownership, so that different teams can own different sets of components, regardless to where they are used - in which page or application.
For example, if you are building an "Airbnb" application, it makes more sense for the "hotels" team to maintain their concrete card-carousels than to demand on a single infra-team for 100% of the components they need.
Next, we use Bit - a tool of our own making now used by thousands of people - to place each component in a front-end "Container". This container is called a Bit "Capsule" and it contains everything the component needs to be used elsewhere: all files, dependancies, build configurations etc. Bit automates this process for you.
Bit applies a standalone version to each component and lets you export a bunch of components to Bit.dev - the component hub we created for teamwork. It's 100% free for open-source and for some private code too.
For example here are the Evangelist components exported to Bit.dev:
And here are the base-ui components exported to bit.dev:
Now we have to sets of components on bit.dev (called "collections"), one for the marketing components and one for the base-ui components.
Each collection is a group of independent components which are standalone versioned, can can be integrated into any new codebase.
There are two ways to integrate a component:
1. Install it as a package from the bit.dev registry using npm/yarn.
2. Use `bit import` to source the component into a new codebase and make local code edits. It's managed by Bit so you keep getting updates.
Note that bit.dev also run CI for every component version, and since it "knows" about the dependents of each component, it lets you make sure every change you make doesn't break other components elsewhere. You can see each component's version build and test results in its page.
It also auto-extracts the docs from react components (TS/JS) using react docgen, to help document each component. And finally, it provides each component with a live playground to try it out and save visual examples.
So now basically you have 2 codebases with 2 different sets of exposed components, each maintained by a different team- while everyone can easily find and use these components in their own pages and applications.
So now let's say you have a 3rd project which is an application you build for your users. You can have more projects and it will work the same way, since everything is now 100% reusable as well.
All you have to do is choose a component and integrate it into your application. As we mentioned above, there are two ways of doing that. Well in fact three, as one and two can be combined together (recommended).
1. You can rely on a regular package-based build-time integration by installing independent components from your bit.dev collections. $
npm i @bit/bit.evangelist.atom.eva-button #or yarn add @bit/bit.evangelist.atom.eva-button
2. You can use `bit import` to source any component's source-code into the consuming application to make local edits. This is very useful for a quick integration that required modifications. As the component is still managed by Bit, you keep getting updates on top of local changes.
bit import bit.evangelist/atom/eva-button
3. Combine the two methods: install components at packages, and when you need a change use `bit import` to quickly get the source-code and make changes right from your application. Then eject and move the component back to begin a package dependency of the application.
One such example is actually the bit.dev homepage! If you head there you will see that it contains components from both Evangelist and base-ui, with different versions, all mixed in one page (!). Cool, right?
The result: your application is now built in a few separate component-codebases, all your components are exposed and made reusable in the cloud, and you can compose and integrate them to build front-end apps.
Ok so finally, here's one more cool aspect of the way we work.
Most teams build a giant monolithic single-versioned component library. We prefer to build with independent components. It's much better to be able to mix&match component versions, get focused relevant updates only to the components you actually use, and make it easy to hotfix/rollback.
You can learn more about this best practice here:
Anyways, since we expose and reuse independent components, we can update specific parts of our UIs and easily control the changes across multiple projects and applications.
For example, when a new version for Evangelist's "eva-button" component will be available, all the pages and applications that use it can get an update to that component, without having to bump versions for any other components.
Any even better, if a developer needs to make some quick changes to the component to fit it into a new app, it can then just use `bit import` to make the changes right from the new project- and even share them back to bit.dev as a new version of the component for future reference.
This makes it much easier not only to adopt and integrate components, but also to make sure every page in every application is always up to date.
* There's a short 5 min video demo that shows this workflow where you get and deliver updates to a component from two different codebases.
Building front-end applications from components maintained in decoupled codebases helps us scale front-end development. It also helps us split ownership of components between developers and product teams.
We use advanced tooling like Bit to expose and integrate components from one codebase to another with the power to balance the advantages of independent components with the ability to scale to many components.
Having all our components shared in one central hub lets our entire dev team collaborate on components, get updates and stay in sync. It also makes it easier to save time on component documentation.
I hope this is interesting for you to read and maybe it can be useful - all or some of it- in your own work. A good place to begin is just to export some components from your app or library to bit.dev and gradually build your reusable component collection. You can split codebases later, or not.
Enjoy and thanks for reading!