Micro frontends (MFEs) are often integrated at runtime rather than build time. This choice isn’t necessarily because runtime integration offers a better user experience—it’s because it enables faster builds and smoother collaboration between teams. With runtime integration, teams can work autonomously, developing, building, and deploying their MFEs independently, often in separate repositories.
However, runtime integration comes with trade-offs. It can lead to inconsistencies in UX, integration challenges, and suboptimal performance. In contrast, build-time integration allows for thorough end-to-end testing, reducing the likelihood of bugs in production. It simplifies dependency management, ensures a consistent UX across the application, and reduces overall bundle(s) size.
The solution presented in this blog implements build-time integration for micro frontends (MFEs) while maintaining team autonomy and ensuring consistency. The key elements of this approach include:
Inversion of Control: MFEs integrate into the app shell based on their own implementation rather than being dictated by the app shell team. MFEs implement an interface that "expects" to be injected with an
app
instance. Theapp
, injected by the application shell, exposes integration slots, global states, and shared functions.Shared Interfaces: The TypeScript interfaces that MFEs need to implement are shared as Bit components, ensuring compatibility and seamless integration.
Shared UI Components: The design system, the elementary UI components for the app, are shared as Bit components to maintain a consistent user experience across all MFEs.
MFEs as Bit Components: Each MFE is released and shared as a Bit component. MFEs are consumed by the app shell as external dependencies (i.e., as packages).
Bit Platform Webhooks: Automatic dependency updates to the application shell when a new MFE is released
Implementing Build-Time MFE Integration with Bit
Bit can be adopted in different ways. Some teams use Bit as a complete development solution, maintaining their applications as deployable Bit components without traditional Git repositories. However, most Bit users integrate it into existing workflows and tools, and that’s the approach we’ll follow in this example.
Our demo solution is a single-page space travel application built with Vite and React, managed in a Git repository. It integrates two Micro Frontends:
Each MFE is managed in its own Git repository and Bit scope.
1. Setting Up Bit Scopes and Git Repositories
The first step is to create Bit scopes and set up Git repositories for the MFEs, their shared UI library, and the orchestration components.
Repository Structure:
- Foundation: Components responsible for orchestrating and integrating MFEs
- Design: Shared UI components ensuring a consistent UX
- Booking: Components for the booking MFE
- Marketing: Components for the marketing MFE
Note: The app shell in this demo is not a Bit component but a standalone application.
Each Bit scope is hosted on Bit Platform, which provides access control features. In this setup, each scope corresponds to a single repository, but a single repository can also contain multiple Bit scopes if needed.
2. Creating the App Shell and Integration Components
The app shell orchestrates MFEs and provides shared dependencies. It consists of two main parts:
1. A Subscriber Component (Bit)
The subscriber defines how MFEs integrate into the system. It provides an API for MFEs to register routes, header links, and shared states.
export class App {
private routes: RouteType[] = [];
private headerLinks: HeaderLink[] = [];
registerRoutes(routes: RouteType[]) {
this.routes.push(...routes);
}
registerHeaderLinks(headerLinks: HeaderLink[]) {
this.headerLinks.push(...headerLinks);
}
renderApp() {
return (
<Theme>
<Header links={this.headerLinks} />
<Routes>
{this.routes.map((route) => (
<Route key={route.path} path={route.path} element={route.element} />
))}
</Routes>
</Theme>
);
}
}
2. The Application (Non-Bit Component)
The application defines which MFEs should be included and handles the build and deployment process.
import { createRoot } from 'react-dom/client';
import { App, Frontend } from '@cosmo-flux/foundation.subscriber';
import { marketingMfe } from '@cosmo-flux/marketing.marketing-mfe';
import { bookingMfe } from '@cosmo-flux/booking.booking-mfe';
const app = new App();
const frontends: Frontend[] = [marketingMfe, bookingMfe];
frontends.forEach((frontend) => frontend(app));
createRoot(document.getElementById('root')!).render(<>{app.renderApp()}</>);
3. Implementing the MFEs
Each MFE follows the Frontend
type defined by the subscriber component. This ensures the MFE teams maintain autonomy while integrating seamlessly.
For example, the Booking MFE registers the Red Planet Reservations page:
import type { Frontend } from '@cosmo-flux/foundation.subscriber';
import { RedPlanetReservations } from '@cosmo-flux/booking.pages.red-planet-reservations';
export const bookingMfe: Frontend = (app) => {
app.registerRoutes([
{ path: '/reservations', element: <RedPlanetReservations /> },
]);
app.registerHeaderLinks([
{ label: 'Reservations', path: '/reservations' },
]);
return app;
};
Because both the MFE and its components are Bit components, they can be imported directly into the app shell project as regular dependencies.
4. Automating Deployment with Webhooks
To replicate the developer experience of runtime MFEs while leveraging build-time integration, we use Bit Webhooks to automatically trigger the app shell’s CI/CD pipeline when a new stable version of an MFE is released.
Automating the build and deployment process might seem risky at first, but in practice, it enhances stability compared to traditional runtime integrations.
Build-time integration enables comprehensive end-to-end testing and automated verifications, ensuring failures occur during the build process rather than at runtime.
Conclusion
By leveraging Bit for build-time integration, teams can enjoy the best of both worlds—independent development with runtime-like autonomy while maintaining stability, performance, and consistency across the entire application.
Using Bit and Bit Platform, components are shared and synced across separate repositories, allowing you to treat your poly-repo setup as one single virtual monorepo.
Top comments (0)