This commit is contained in:
2025-08-31 01:33:35 -04:00
parent 01c940ea31
commit d05af3b385
14 changed files with 236 additions and 92 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,9 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
// Import the microfrontends registry
const { getExposesConfig } = require('../web/src/microfrontends.js');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
@ -38,9 +41,7 @@ module.exports = {
new ModuleFederationPlugin({
name: 'demo',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx',
},
exposes: getExposesConfig('demo'),
shared: {
react: {
singleton: true,
@ -73,4 +74,4 @@ module.exports = {
template: './public/index.html',
}),
],
};
};

1
faas/web/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -2,6 +2,9 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const webpack = require('webpack');
// Import the microfrontends registry
const { getExposesConfig } = require('../../web/src/microfrontends.js');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
@ -39,9 +42,7 @@ module.exports = {
new ModuleFederationPlugin({
name: 'faas',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx',
},
exposes: getExposesConfig('faas'),
shared: {
react: {
singleton: true,
@ -82,4 +83,4 @@ module.exports = {
'process.env': JSON.stringify(process.env),
}),
],
};
};

2
kms/web/dist/665.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,11 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const webpack = require('webpack');
// Import the microfrontends registry
const { getExposesConfig } = require('../../web/src/microfrontends.js');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
@ -39,9 +43,7 @@ module.exports = {
new ModuleFederationPlugin({
name: 'kms',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx',
},
exposes: getExposesConfig('kms'),
shared: {
react: {
singleton: true,
@ -82,4 +84,4 @@ module.exports = {
'process.env': JSON.stringify(process.env),
}),
],
};
};

2
web/dist/main.js vendored

File diff suppressed because one or more lines are too long

View File

@ -3,10 +3,22 @@ import { useParams } from 'react-router-dom';
import { Loader, Center, Text, Stack, Alert } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
import Breadcrumbs from './Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
const DemoApp = React.lazy(() => import('demo/App'));
const KMSApp = React.lazy(() => import('kms/App'));
const FaaSApp = React.lazy(() => import('faas/App'));
// Pre-defined lazy imports for each microfrontend
// @ts-ignore - These modules are loaded at runtime via Module Federation
const DemoApp = React.lazy(() => import('demo/src/App'));
// @ts-ignore - These modules are loaded at runtime via Module Federation
const KMSApp = React.lazy(() => import('kms/src/App'));
// @ts-ignore - These modules are loaded at runtime via Module Federation
const FaaSApp = React.lazy(() => import('faas/src/App'));
// Map app names to components
const appComponents: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
demo: DemoApp,
kms: KMSApp,
faas: FaaSApp,
};
const AppLoader: React.FC = () => {
const { appName } = useParams<{ appName: string }>();
@ -32,32 +44,21 @@ const AppLoader: React.FC = () => {
);
const renderApp = () => {
switch (appName) {
case 'demo':
return (
<Suspense fallback={<LoadingFallback />}>
<DemoApp />
</Suspense>
);
case 'kms':
return (
<Suspense fallback={<LoadingFallback />}>
<KMSApp />
</Suspense>
);
case 'faas':
return (
<Suspense fallback={<LoadingFallback />}>
<FaaSApp />
</Suspense>
);
default:
return (
<ErrorFallback
error={`Application "${appName}" is not available or not configured.`}
/>
);
// Check if the app is registered in our microfrontends registry
if (appName && appComponents[appName] && microFrontends[appName]) {
const App = appComponents[appName];
return (
<Suspense fallback={<LoadingFallback />}>
<App />
</Suspense>
);
}
return (
<ErrorFallback
error={`Application "${appName}" is not available or not configured.`}
/>
);
};
return (
@ -68,4 +69,4 @@ const AppLoader: React.FC = () => {
);
};
export default AppLoader;
export default AppLoader;

View File

@ -1,44 +1,89 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { NavLink, Stack, Text } from '@mantine/core';
import { useNavigate, useLocation } from 'react-router-dom';
import {
IconDashboard,
IconKey,
IconFunction,
IconApps,
} from '@tabler/icons-react';
import { microFrontends } from '../microfrontends.js';
// Map microfrontend IDs to display information
const getAppInfo = (id: string) => {
const appInfo: Record<string, { label: string; icon: React.ComponentType<any>; category: string }> = {
demo: {
label: 'Demo App',
icon: IconDashboard,
category: 'Development',
},
kms: {
label: 'Key Management',
icon: IconKey,
category: 'Security',
},
faas: {
label: 'Functions',
icon: IconFunction,
category: 'Development',
},
};
return appInfo[id] || {
label: id.charAt(0).toUpperCase() + id.slice(1),
icon: IconApps,
category: 'Application',
};
};
const Navigation: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
// Define all available applications
const allAvailableApplications = [
{
label: 'Demo App',
icon: IconDashboard,
path: '/app/demo',
},
{
label: 'Key Management',
icon: IconKey,
path: '/app/kms',
},
{
label: 'Functions',
icon: IconFunction,
path: '/app/faas',
},
];
// Load favorites from localStorage (same as HomePage)
const [favoriteApps, setFavoriteApps] = useState<string[]>(() => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
return saved ? JSON.parse(saved) : ['demo'];
} catch {
return ['demo'];
}
});
// Define which apps are favorited (you could make this dynamic later)
const favoritePaths = ['/app/demo'];
// Listen for changes to favorites in localStorage (in case they change in HomePage)
useEffect(() => {
const handleStorageChange = () => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
if (saved) {
setFavoriteApps(JSON.parse(saved));
}
} catch {
// If parsing fails, keep current state
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, []);
// Generate all available applications from the microfrontends registry
const allAvailableApplications = Object.keys(microFrontends).map(id => {
const info = getAppInfo(id);
return {
id,
label: info.label,
icon: info.icon,
path: `/app/${id}`,
};
});
const favoriteApplications = allAvailableApplications.filter(app =>
favoritePaths.includes(app.path)
favoriteApps.includes(app.id)
);
const nonFavoriteApplications = allAvailableApplications.filter(app =>
!favoritePaths.includes(app.path)
!favoriteApps.includes(app.id)
);
return (
@ -78,4 +123,4 @@ const Navigation: React.FC = () => {
);
};
export default Navigation;
export default Navigation;

59
web/src/microfrontends.js Normal file
View File

@ -0,0 +1,59 @@
// Centralized registry for all microfrontends
// This file contains the single source of truth for all microfrontend configurations
// Define all microfrontends
export const microFrontends = {
demo: {
name: 'demo',
url: 'http://localhost:3001',
port: 3001,
exposedModule: './src/App',
importPath: 'demo/src/App',
},
kms: {
name: 'kms',
url: 'http://localhost:3002',
port: 3002,
exposedModule: './src/App',
importPath: 'kms/src/App',
},
faas: {
name: 'faas',
url: 'http://localhost:3003',
port: 3003,
exposedModule: './src/App',
importPath: 'faas/src/App',
},
};
// Generate remotes configuration for Module Federation
export const getRemotesConfig = () => {
const remotes = {};
Object.values(microFrontends).forEach(mf => {
remotes[mf.name] = `${mf.name}@${mf.url}/remoteEntry.js`;
});
return remotes;
};
// Generate exposes configuration for Module Federation (for microfrontends)
export const getExposesConfig = (appName) => {
const appConfig = microFrontends[appName];
if (!appConfig) {
throw new Error(`Microfrontend ${appName} not found in registry`);
}
const exposes = {};
exposes[appConfig.exposedModule] = appConfig.exposedModule;
return exposes;
};
// Get app loader configuration for AppLoader component
export const getAppLoaderConfig = () => {
return Object.keys(microFrontends).map(name => ({
name,
importPath: microFrontends[name].importPath,
}));
};

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import {
SimpleGrid,
Card,
@ -16,8 +16,10 @@ import {
IconDashboard,
IconKey,
IconFunction,
IconApps,
} from '@tabler/icons-react';
import Breadcrumbs from '../components/Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
interface App {
id: string;
@ -28,36 +30,71 @@ interface App {
category: string;
}
const HomePage: React.FC = () => {
const navigate = useNavigate();
const [favoriteApps, setFavoriteApps] = useState<string[]>(['demo']);
const availableApps: App[] = [
{
id: 'demo',
// Map microfrontend IDs to display information
const getAppInfo = (id: string) => {
const appInfo: Record<string, { name: string; description: string; icon: React.ComponentType<any>; category: string }> = {
demo: {
name: 'Demo App',
description: 'Sample microfrontend application for testing module federation',
icon: IconDashboard,
path: '/app/demo',
category: 'Development',
},
{
id: 'kms',
kms: {
name: 'Key Management System',
description: 'Manage API keys, tokens, and access permissions for applications',
icon: IconKey,
path: '/app/kms',
category: 'Security',
},
{
id: 'faas',
faas: {
name: 'Function-as-a-Service',
description: 'Deploy and manage serverless functions',
icon: IconFunction,
path: '/app/faas',
category: 'Development',
},
];
};
return appInfo[id] || {
name: id.charAt(0).toUpperCase() + id.slice(1),
description: `Microfrontend application: ${id}`,
icon: IconApps,
category: 'Application',
};
};
const HomePage: React.FC = () => {
const navigate = useNavigate();
// Load favorites from localStorage or default to demo app
const [favoriteApps, setFavoriteApps] = useState<string[]>(() => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
return saved ? JSON.parse(saved) : ['demo'];
} catch {
return ['demo'];
}
});
// Save favorites to localStorage whenever they change
useEffect(() => {
try {
localStorage.setItem('favoriteMicrofrontends', JSON.stringify(favoriteApps));
} catch (e) {
console.warn('Failed to save favorites to localStorage', e);
}
}, [favoriteApps]);
// Generate available apps from the microfrontends registry
const availableApps: App[] = Object.keys(microFrontends).map(id => {
const info = getAppInfo(id);
return {
id,
name: info.name,
description: info.description,
icon: info.icon,
path: `/app/${id}`,
category: info.category,
};
});
const toggleFavorite = (appId: string) => {
setFavoriteApps(prev =>

View File

@ -1,5 +1,6 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const { getRemotesConfig } = require('./src/microfrontends.js');
module.exports = {
mode: 'development',
@ -44,11 +45,7 @@ module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
demo: 'demo@http://localhost:3001/remoteEntry.js',
kms: 'kms@http://localhost:3002/remoteEntry.js',
faas: 'faas@http://localhost:3003/remoteEntry.js',
},
remotes: getRemotesConfig(),
shared: {
react: {
singleton: true,
@ -86,4 +83,4 @@ module.exports = {
template: './public/index.html',
}),
],
};
};