Module Federation: Building a Micro Frontends Solution in 2024
Tags: javascript, webdev, typescript, frontend
Published: 2024-07-03 19:01:10

In this blog, we’ll use [Module Federation](https://module-federation.io/) and [Bit](https://bit.cloud) to implement a runtime integration of Micro Frontends. 

We'll cover the following topics:

1. Using Bit’s templates to generate host applications and remote modules
2. Managing ModFed shared dependencies as a Bit component
3. Creating a “plugin system” via [shared types](https://bit.cloud/bit-bazaar/shell-app/types/navigation/~api-reference) that allows remote modules to integrate into the host application in various ways
4. Running remote modules in development within the context of their host application

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*IhWUeHzGmF-peQNZccFn_A.png)

> [The deployed solution](https://bit-bazaar.netlify.app/)

<br>

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*YcwnsOHOAQ6aufcGQWEnCw.png)

> [The Bit org that maintains the MFEs solution](https://bit.cloud/bit-bazaar/~scopes)

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*6nlxFEHjxyt3He3DICxWJQ.png)

> [The repos maintaining the Bit workspaces for the shared components, host app, and remote modules](https://github.com/orgs/bit-bazaar/repositories)

<br>

## Host Apps and Remote Modules

The host app and remote modules were generated using pre-configured templates (made available by Bit):

```bash
npx @teambit/bvm install # install Bit
bit init my-modfed-solution # create a new Bit workspace
cd my-modfed-solution
```

Add the following to your `workspace.jsonc` to make the ModFed templates available in your workspace:

```json
"teambit.generator/generator": {
    "envs": [
      "frontend.module-federation/envs/mf-react-env"
    ]
  }
```
Run the following commands:

```bash
bit install # install the workspace dependnecies
bit create modfed-remote-mfe storefront # generate a remote module
bit create modfded-remote-mfe blog
bit create modfed-host-app shell-app # generate a host app
```

> Run `bit templates` to list the available ModFed templates

To list the available apps (and remote modules), run:

```bash
bit app list
```

The output lists the component IDs and their corresponding app names:

```plaintext
┌─────────────────────────────────────────────────┬─────────────────────┐
│ id                                              │ name                │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.storefront/storefront                │ storefront          │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.blog/blog                            │ blog                │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.shell-app/shell-app                  │ shell-app           │
└─────────────────────────────────────────────────┴─────────────────────┘
```
You can run the apps locally by using their app name:

```bash
bit run storefront
```

## Shared Dependencies

Our solution consists of many shared dependencies configured to be excluded from the app bundles and loaded as separate chunks. This is one of ModFed’s strengths. It allows us to optimize our bundle size, maintain consistency, and avoid conflicts between versions of the same module.

Our shared dependencies are maintained as a Bit component shared across projects (the host app and the remote modules). This allows teams to maintain consistency while working independently.

The list of shared dependencies consists primarily of runtime libs and a design system:

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*SDBgYqyMyiTVR3MmXrCVtw.png)

> [The ‘shared dependencies’ component](https://bit.cloud/bit-bazaar/shell-app/shared-dependencies/~code/shared-dependencies.ts) (which lists the shred deps) is used by the host app config and remote modules config

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*owibIMnKJ0s749r8DEwWvw.png)

> [https://bit.cloud/bit-bazaar/shell-app/shared-dependencies/~code/shared-dependencies.ts](https://bit.cloud/bit-bazaar/shell-app/shared-dependencies/~code/shared-dependencies.ts)

For example:

```typescript
/**
 * @filename: storefront.bit-app.ts
 * @component-id: bit-bazaar.storefront/storefront
*/

import { MfReact } from '@frontend/module-federation.react.apps-types.mf-rspack';
/* import the 'shared dependnecies' components */
import { shellAppSharedDependencies } from '@bit-bazaar/shell-app.shared-dependencies';


export default MfReact.from({
  name: 'storefront',
  clientRoot: './storefront.app-root.js',
  moduleFederation: {
    exposes: {
      // ...
    },
    shared: shellAppSharedDependencies,
  }
});
```

## A Shared Design System

Our component library and theme are based on Material UI. They are maintained in the “design” scope and shared across Micro Frontends.

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*VNkzhuxM7aQiLslASgYs9A.png)

> [The “design” scope which contains the shared UI components and themes](https://bit.cloud/bit-bazaar/design)

## Shared Context

The ‘Theme Provider,’ ‘Auth Provider,’ and other context components are part of the “host app” or “shell app.” As such, they are maintained by the “shell app” team.

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*gzRW_UPh8FVNg6AdunoDOg.png)

> The context for all MFEs (provided by the [shell app](https://bit.cloud/bit-bazaar/shell-app/shell-app/~code/shell-app.tsx))

Teams working on MFEs do not need to bother with authentication, authorization, or any other shared functionality. The “host” or “shell” team provides it all to them.

For example, if team Storefront needs to implement functionality based on the user auth, they would explore the ‘shell app’ scope and look for the proper “SDK”.

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*nygdqeWAhwyCEkAhZSH8Ww.png)


> [The “auth” context and hook](https://bit.cloud/bit-bazaar/shell-app/auth/auth-provider/~code)

## Routing and Navigation

The shell app provides a sort of “plugin system” where Micro Frontends (remote modules) can integrate into it in ways that go beyond a simple link. It does so by providing the types for each “plugin”.

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*LNIly_jIcZHgyl67TluvKg.png)

> [The shared “navigation item” type](https://bit.cloud/bit-bazaar/shell-app/types/navigation)

For example, a remote module can implement a “navigation item” interface that includes its navigation options.

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*O5Z7c-movW0_IlQZ-W4kMw.png)

This can then be exposed for the shell app (which will load it at runtime):


```typescript
/**
 * @filename: blog.bit-app.ts
 * @component-id: bit-bazaar.blog/blog
*/

export default MfReact.from({
  name: 'blog',
  clientRoot: './blog.app-root.js',
  moduleFederation: {
    exposes: {
      /** 
       * the MFE navigation exposed to be loaded 
       * by the shell app at runtime
       **/
      './blognav': './navitem.js',
       /**
        * the main chunk of the 'blog' MFE
        **/
      './blog': './blog.js',
    },
    shared: shellAppSharedDependencies,
  },
  deploy: Netlify.deploy(netlifyConfig),
});
```

The routing is handled at the level that suits the module. For example, the shell app only handles routing to `/blog/*` and `/storefront/*`. It does not determine the routing “inside” each MFE (such as `storefront/products`).

```typescript
/**
 * @filename: shell-app.tsx
 * @component-id: bit-bazaar.shell-app/shell-app
*/

export function ShellApp() {
  return (
    <BrowserRouter>
          <Routes>
            <Route path="/" element={<Layout />}>
              <Route index element={<Homepage />} />
              <Route path="store/*" element={<Store />} />
              <Route path="blog/*" element={<Blog />} />
              <Route path="*" element={<div>Not Found</div>} />
            </Route>
          </Routes>
    </BrowserRouter>
  );
```

Accordingly, remote modules, such as the `blog`, are not responsible for the `/blog/*` routing (the routing to the blog MFE)—only for nested routes.

```typescript
/**
 * @filename: blog.tsx
 * @component-id: bit-bazaar.blog/blog
*/

export function Blog() {
  return (
      <Routes>
        <Route path="articles" element={<ArticlesPage />} />
        <Route path="categories" element={<CategoriesPage />} />
      </Routes>
  );
}
```

## DevX

For the ultimate dev experience, each team uses a “Platform” component to consume an immutable version of the shell app and possibly other remote modules.

This provides the MFEs with the proper context to run in development. It ensures a consistent and seamless dev experience while properly enforcing permissions and access control (e.g., the 'blog' team cannot modify the 'storefront' MFE or the shell app).

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*NU2_MVKTnTMHAEfUr_5QBw.png)

> [https://bit.cloud/bit-bazaar/storefront/storefront-platform/~code/shell-platform.bit-app.ts](https://bit.cloud/bit-bazaar/storefront/storefront-platform/~code/shell-platform.bit-app.ts)

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*kxmzE2RnzgLq70WKtnKPSw.png)

> The 'shell-app' as an immutable dependency of the ‘storefront-platform’ used by the ‘storefront’ team for ‘storefront’ development in full context

For example, the ‘storefront’ team are able to run the ‘storefront’ MFE in its full context (shell app and even other MFEs) by running the 'platform' app maintained by them (for development only):

```
bit run storefront-platform
```

![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*990s5cPwdR9-tYjigxgNbw.png)

You can generate a 'Platform' component using the template provided by the ModFed env (the one configured as a generator at the beginning of this blog):

```
bit create modfed-platform my-platform
```