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)