-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Open
Description
Approach: Dynamic module federation setup using following packages
"@module-federation/enhanced": "^0.19.1",
"@module-federation/vite": "^1.8.1",
- I am creating instance using createInstance as first thing when app loads
- Take the instance object and using it to later register and loadRemote
- Ideally this should work but I keep getting error Please call createInstance first on first page load
Error: Please call createInstance first. Error Code: RUNTIME-009** for first page load**, after that it loads fine without the error.
4. Its failing at this line when trying to loadRemote
5. const module = await this.fedInstance.loadRemote<T>(`${remoteName}/${exposedModule}`);
if (!module) {
throw new Error(`Module ${remoteName}/${exposedModule} is null or undefined`);
}
return module as NonNullable<T>;
- I have looked into global instances created. On page load it creates instance of FederationHost with my remotes in it.
- When it tries to load RemoteApp it creates another instances ModuleFederation
- Then it throws error
- ** What could be the reason of this error in below setup ?**
- I have tried with different versions of these packages using old init function as well but in the end it leads to same behaviour.
Appreciate any suggestions to fix this issue
SETUP:
//1. Main.tsx
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { createInstance } from '@module-federation/enhanced/runtime';
import { BrowserRouter } from 'react-router-dom';
// Create federation instance
export const federationInstance = createInstance({
name: 'shell',
remotes: [],
shared: {
react: {
version: '18.3.1',
scope: 'default',
lib: () => import('react'),
shareConfig: {
singleton: true,
eager: true,
requiredVersion: '>=18.3.1',
strictVersion: false,
},
},
'react-dom': {
version: '18.3.1',
scope: 'default',
lib: () => import('react-dom'),
shareConfig: {
singleton: true,
eager: true,
requiredVersion: '>=18.3.1',
strictVersion: false,
},
},
'react-router-dom': {
version: '7.6.0',
scope: 'default',
lib: () => import('react-router-dom'),
shareConfig: {
singleton: true,
eager: true,
requiredVersion: '~7.6.0',
strictVersion: false,
},
},
},
});
FrontendService.initialise(federationInstance); /
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(<LandingPage />)
// 2. FrontendService.ts
**import { loadRemote, registerRemotes, getInstance } from '@module-federation/enhanced/runtime';**
import React from 'react';
import { lazy } from 'react';
import { RouteObject } from 'react-router-dom';
export interface MicroFrontendConfig {
name: string;
url: string;
exposedModule: string;
routePath: string;
moduleName?: string;
routeData?: any;
}
export class FrontendService {
private static registeredRemotes = new Set<string>();
private static isInitialized = false;
private static fedInstance ;
static async initialise( instance ) {
this.fedInstance = instance;
}
static async registerRemote(config: MicroFrontendConfig): Promise<void> {
// Ensure federation is initialized
await this.ensureInitialized();
if (this.registeredRemotes.has(config.name)) {
return;
}
await this.fedInstance.registerRemotes([
{
name: config.name,
entry: config.url,
alias: config.name,
type: 'module',
},
]);
this.registeredRemotes.add(config.name);
}
/**
* Load a remote module dynamically
*/
static async loadRemoteModule<T = any>(
remoteName: string,
exposedModule: string,
): Promise<NonNullable<T>> {
try {
const module = await this.fedInstance.loadRemote<T>(`${remoteName}/${exposedModule}`);
if (!module) {
throw new Error(`Module ${remoteName}/${exposedModule} is null or undefined`);
}
return {
default: module[moduleName] || module.default || module,
};
} catch (error) {
console.error(`Failed to load remote module ${remoteName}/${exposedModule}:`, error);
throw error;
}
}
static createRemoteComponent(config: MicroFrontendConfig): React.ComponentType<any> {
const moduleName = config.moduleName || 'default';
return lazy(async () => {
await this.registerRemote(config);
const component= await this.loadRemoteModule(config.name, config.exposedModule || './App');
return component;
});
}
static setupMicroFrontendRoute(config: MicroFrontendConfig): RouteObject {
const RemoteComponent = this.createRemoteComponent(config);
return {
path: config.routePath,
element: React.createElement(RemoteComponent),
// Pass any additional route data
...(config.routeData ? { handle: config.routeData } : {}),
};
}
static createMicroFrontendRoutes(configs: MicroFrontendConfig[]): RouteObject[] {
return configs.map((config) => this.setupMicroFrontendRoute(config));
}
static async preloadRemoteEntries(configs: MicroFrontendConfig[]): Promise<void[]> {
// Ensure federation is initialized first
return Promise.all(configs.map((config) => this.registerRemote(config)));
}
}
// 3. LandingPage.tsx
import { lazy, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Suspense, useEffect, useState } from 'react';
import { createBrowserRouter, Link, Navigate, Outlet, RouteObject, RouterProvider, useLocation,} from 'react-router-dom';
import { FrontendConfig, FrontendService } from '../services/FrontendService';
export function LandingPage() {
const [router, setRouter] = useState<ReturnType<typeof createBrowserRouter> | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const initializeMicroFrontends = async () => {
try {
const microFrontends: MicroFrontendConfig[] = [
{
name: 'remoteApp',
url: 'http://localhost:5050/remoteEntry.js',
exposedModule: 'RemoteMarketApp',
moduleName: 'RemoteMarketApp',
routePath: '/remote-market/*',
routeData: { title: remote-market' },
},
];
**// Preload remote entries for better performance
await FrontendService.preloadRemoteEntries(microFrontends);
// Create dynamic routes
const dynamicRoutes = FrontendService.createMicroFrontendRoutes(microFrontends);**
// Define all routes
const routes: RouteObject[] = [
{
path: '/',
element: <Layout />, // Layout with nav and outlet
children: [
{ path: '/', element: <Navigate to="/remote-market" replace /> },
{ path: 'profile', element: <Profile /> },
...dynamicRoutes,
],
},
];
// Create router
const browserRouter = createBrowserRouter(routes);
setRouter(browserRouter);
} catch (error) {
console.error('Failed to initialize micro frontends:', error);
} finally {
setLoading(false);
}
};
initializeMicroFrontends();
}, []);
if (loading || !router) {
return (
<div >
<div>Loading micro frontends...</div>
</div>
);
}
return (
<div>
<div >
<h3>UI : Shell</h3>
<h6>Shell container for UI application</h6>
<Suspense fallback={<div>Loading app...</div>}>
<ErrorBoundary>
<RouterProvider router={router} />
</ErrorBoundary>
</Suspense>
</div>
</div>
);
}
//**vite.config.ts**
import { defineConfig } from 'vite';
export default defineConfig(({ mode }) => ({
root: __dirname,
server: {
port: 1010,
host: 'localhost',
fs: {
strict: false,
},
},
}
)
);
// REMOTE MARKET APP's **vite.config.ts**
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { federation } from '@module-federation/vite';
export default defineConfig(({ mode }) => {
return {
root: __dirname,
server: {
port: 5050,
host: 'localhost',
cors: true,
},
plugins: [
react(),
federation({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./RemoteMarketApp': './src/remote/RemoteMarketApp.tsx',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.3.1',
strictVersion: false,
},
'react-dom': {
singleton: true,
requiredVersion: '^18.3.1',
strictVersion: false,
},
'react-router-dom': {
singleton: true,
requiredVersion: '^7.6.0',
strictVersion: false,
},
},
}),
],
};
}
);
Metadata
Metadata
Assignees
Labels
No labels