Combining Native Federation and Module Federation

  1. Micro Frontends with Modern Angular – Part 1: Standalone and esbuild
  2. Micro Frontends with Modern Angular – Part 2: Multi-Version and Multi-Framework Solutions with Angular Elements and Web Components
  3. Combining Native Federation and Module Federation

A question we frequently get is how to add Native Federation to a system originally built with Module Federation. This can help with a gradual migration and also give feature teams the choice of which Federation flavor to use.

This article demonstrates how this can be accomplished using the Module Federation Runtime in a Native Federation Shell.

📂 Source Code

Example Application

The example application used for this article is a simple shell that loads both, Module Federation and Native Federation based remotes. Dependecies are shared between the two worlds:

Prerequisites

Create a shell using Native Federation and add the Module Federation Runtime for loading Module Federation remotes. This runtime is part of the @module-federation/enhanced package:

npm i @module-federation/enhanced

In all Native Federation projects where the Module Federation runtime is used or installed, skip it from being shared in your federation.config.js:

module.exports = withNativeFederation({
  ...
  skip: [
    ...
    /^@module-federation/,  
        // Use RegExp to skip ALL entry points!
  ]
});

Initializing the Shell

When initializing the shell, initialize both, Native Federation and Module Federation:

// main.ts
import { initFederation as initNativeFederation } from '@angular-architects/native-federation';
import { init as initModuleFederation } from '@module-federation/enhanced/runtime';
import { getShared } from './app/shared/federatio-helpers';

(async () => {
  // Step 1: Initialize Native Federation
  await initNativeFederation('federation.manifest.json');

  // Step 2: Get metadata about libs shared via Native Federation
  const shared = getShared();

  // Step 3: Initialize Module Federation
  //  Remarks: Consider loading this MF config via the fetch API
  initModuleFederation({
    name: 'shell',
    remotes: [
      {
        name: 'modfed-mf',
        entry: 'http://localhost:4201/remoteEntry.js',
        type: 'esm',
      },
      {
        name: 'react',
        entry:
          'https://witty-wave-0a695f710.azurestaticapps.net/remoteEntry.js',
      },
      {
        name: 'angular2',
        entry: 'https://gray-pond-030798810.azurestaticapps.net/remoteEntry.js',
      },
      {
        name: 'angular3',
        entry:
          'https://gray-river-0b8c23a10.azurestaticapps.net/remoteEntry.js',
      }
    ],
    // Step 3a: Delegate shared libs from Native Federation
    shared,
  })
  .initializeSharing();

  // Step 4: Delegate to file bootstrapping the SPA
  await import('./bootstrap');
})();

The helper function getShared (see source code here) returns metadata about the libs shared via Native Federation. This metadata is forwarded to the Module Federation config to bridge both worlds.

Loading the Micro Frontends

For loading the Micro Frontends use the respective helper from @module-federation/enhanced/runtime for Module Federation-based Micro Frontends and @angular-architects/native-federation (or @softarc/native-federation if you don't use Angular) for Native Federation-based ones:

...
import { loadRemote as loadModuleRemote } from '@module-federation/enhanced/runtime';
import { loadRemoteModule as loadNativeRemote } from '@angular-architects/native-federation';

export const routes: Routes = [
    {
        path: '',
        pathMatch: 'full',
        component: HomeComponent,
    },

    // MF-based Micro Frontend
    {
        path: 'modfed-mf',
        loadComponent: () => loadModuleRemote<any>('modfed-mf/Component').then(m => m.AppComponent)
    },

    // NF-based Micro Frontend
    {
        path: 'natfed-mf',
        loadComponent: () => loadNativeRemote('natfed-mf', './Component').then(m => m.AppComponent)
    },

    ...
]

For loading further frameworks in different versions, the example also uses a Wrapper component that loads Web Components using Federation. This approach is described here.

Further Steps

While this example shows how to bring both worlds together, in an real-world example you might want to abstract both options a bit more. For instance, you might want to load the configuration for the Module Federation Runtime from a file, similar to the federation.manifest.json used for Native Federation.

As an alternative, you might get metadata for both worlds from a service and a custom helper function could delegate the respective entries to Module and Native Federation during initialization. This metadata could also be used in a further helper function for loading a remote to decided wether to load it via Native oder Module Federation.

More on this: Angular Architecture Workshop (online, interactive, advanced)

Become an expert for enterprise-scale and maintainable Angular applications with our Angular Architecture workshop!

All Details (English Workshop) | All Details (German Workshop)

eBook: Micro Frontends and Moduliths with Angular

Learn how to build enterprise-scale Angular applications which are maintainable in the long run

✓ 20 chapters
✓ source code examples
✓ PDF, epub (Android and iOS) and mobi (Kindle)

Get it for free!