DEV Community

Cover image for Module Federation: Building a Micro Frontends Solution in 2024
Eden Ella for Bit

Posted on • Edited on • Originally published at Medium

Module Federation: Building a Micro Frontends Solution in 2024

In this blog, we’ll use Module Federation and Bit 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 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

The deployed solution

The Bit org that maintains the MFEs solution

The repos maintaining the Bit workspaces for the shared components, host app, and remote modules

Host Apps and Remote Modules

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

npx @teambit/bvm install # install Bit
bit init my-modfed-solution # create a new Bit workspace
cd my-modfed-solution
Enter fullscreen mode Exit fullscreen mode

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

"teambit.generator/generator": {
    "envs": [
      "frontend.module-federation/envs/mf-react-env"
    ]
  }
Enter fullscreen mode Exit fullscreen mode

Run the following commands:

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
Enter fullscreen mode Exit fullscreen mode

Run bit templates to list the available ModFed templates

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

bit app list
Enter fullscreen mode Exit fullscreen mode

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

┌─────────────────────────────────────────────────┬─────────────────────┐
│ id                                              │ name                │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.storefront/storefront                │ storefront          │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.blog/blog                            │ blog                │
├─────────────────────────────────────────────────┼─────────────────────┤
│ bit-bazaar.shell-app/shell-app                  │ shell-app           │
└─────────────────────────────────────────────────┴─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

You can run the apps locally by using their app name:

bit run storefront
Enter fullscreen mode Exit fullscreen mode

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:

The ‘shared dependencies’ component (which lists the shred deps) is used by the host app config and remote modules config

https://bit.cloud/bit-bazaar/shell-app/shared-dependencies/~code/shared-dependencies.ts

For example:

/**
 * @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,
  }
});
Enter fullscreen mode Exit fullscreen mode

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.

The “design” scope which contains the shared UI components and themes

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.

The context for all MFEs (provided by the shell app)

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”.

The “auth” context and hook

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”.

The shared “navigation item” type

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

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

/**
 * @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),
});
Enter fullscreen mode Exit fullscreen mode

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).

/**
 * @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>
  );
Enter fullscreen mode Exit fullscreen mode

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

/**
 * @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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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://bit.cloud/bit-bazaar/storefront/storefront-platform/~code/shell-platform.bit-app.ts

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)