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 HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container; const { ModuleFederationPlugin } = require('webpack').container;
// Import the microfrontends registry
const { getExposesConfig } = require('../web/src/microfrontends.js');
module.exports = { module.exports = {
mode: 'development', mode: 'development',
entry: './src/index.tsx', entry: './src/index.tsx',
@ -38,9 +41,7 @@ module.exports = {
new ModuleFederationPlugin({ new ModuleFederationPlugin({
name: 'demo', name: 'demo',
filename: 'remoteEntry.js', filename: 'remoteEntry.js',
exposes: { exposes: getExposesConfig('demo'),
'./App': './src/App.tsx',
},
shared: { shared: {
react: { react: {
singleton: true, singleton: true,
@ -73,4 +74,4 @@ module.exports = {
template: './public/index.html', 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 { ModuleFederationPlugin } = require('webpack').container;
const webpack = require('webpack'); const webpack = require('webpack');
// Import the microfrontends registry
const { getExposesConfig } = require('../../web/src/microfrontends.js');
module.exports = { module.exports = {
mode: 'development', mode: 'development',
entry: './src/index.tsx', entry: './src/index.tsx',
@ -39,9 +42,7 @@ module.exports = {
new ModuleFederationPlugin({ new ModuleFederationPlugin({
name: 'faas', name: 'faas',
filename: 'remoteEntry.js', filename: 'remoteEntry.js',
exposes: { exposes: getExposesConfig('faas'),
'./App': './src/App.tsx',
},
shared: { shared: {
react: { react: {
singleton: true, singleton: true,
@ -82,4 +83,4 @@ module.exports = {
'process.env': JSON.stringify(process.env), '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 HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container; const { ModuleFederationPlugin } = require('webpack').container;
const webpack = require('webpack'); const webpack = require('webpack');
// Import the microfrontends registry
const { getExposesConfig } = require('../../web/src/microfrontends.js');
module.exports = { module.exports = {
mode: 'development', mode: 'development',
entry: './src/index.tsx', entry: './src/index.tsx',
@ -39,9 +43,7 @@ module.exports = {
new ModuleFederationPlugin({ new ModuleFederationPlugin({
name: 'kms', name: 'kms',
filename: 'remoteEntry.js', filename: 'remoteEntry.js',
exposes: { exposes: getExposesConfig('kms'),
'./App': './src/App.tsx',
},
shared: { shared: {
react: { react: {
singleton: true, singleton: true,
@ -82,4 +84,4 @@ module.exports = {
'process.env': JSON.stringify(process.env), '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 { Loader, Center, Text, Stack, Alert } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react'; import { IconAlertCircle } from '@tabler/icons-react';
import Breadcrumbs from './Breadcrumbs'; import Breadcrumbs from './Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
const DemoApp = React.lazy(() => import('demo/App')); // Pre-defined lazy imports for each microfrontend
const KMSApp = React.lazy(() => import('kms/App')); // @ts-ignore - These modules are loaded at runtime via Module Federation
const FaaSApp = React.lazy(() => import('faas/App')); 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 AppLoader: React.FC = () => {
const { appName } = useParams<{ appName: string }>(); const { appName } = useParams<{ appName: string }>();
@ -32,32 +44,21 @@ const AppLoader: React.FC = () => {
); );
const renderApp = () => { const renderApp = () => {
switch (appName) { // Check if the app is registered in our microfrontends registry
case 'demo': if (appName && appComponents[appName] && microFrontends[appName]) {
return ( const App = appComponents[appName];
<Suspense fallback={<LoadingFallback />}> return (
<DemoApp /> <Suspense fallback={<LoadingFallback />}>
</Suspense> <App />
); </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.`}
/>
);
} }
return (
<ErrorFallback
error={`Application "${appName}" is not available or not configured.`}
/>
);
}; };
return ( 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 { NavLink, Stack, Text } from '@mantine/core';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { import {
IconDashboard, IconDashboard,
IconKey, IconKey,
IconFunction, IconFunction,
IconApps,
} from '@tabler/icons-react'; } 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 Navigation: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
// Define all available applications // Load favorites from localStorage (same as HomePage)
const allAvailableApplications = [ const [favoriteApps, setFavoriteApps] = useState<string[]>(() => {
{ try {
label: 'Demo App', const saved = localStorage.getItem('favoriteMicrofrontends');
icon: IconDashboard, return saved ? JSON.parse(saved) : ['demo'];
path: '/app/demo', } catch {
}, return ['demo'];
{ }
label: 'Key Management', });
icon: IconKey,
path: '/app/kms',
},
{
label: 'Functions',
icon: IconFunction,
path: '/app/faas',
},
];
// Define which apps are favorited (you could make this dynamic later) // Listen for changes to favorites in localStorage (in case they change in HomePage)
const favoritePaths = ['/app/demo']; 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 => const favoriteApplications = allAvailableApplications.filter(app =>
favoritePaths.includes(app.path) favoriteApps.includes(app.id)
); );
const nonFavoriteApplications = allAvailableApplications.filter(app => const nonFavoriteApplications = allAvailableApplications.filter(app =>
!favoritePaths.includes(app.path) !favoriteApps.includes(app.id)
); );
return ( 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 { import {
SimpleGrid, SimpleGrid,
Card, Card,
@ -16,8 +16,10 @@ import {
IconDashboard, IconDashboard,
IconKey, IconKey,
IconFunction, IconFunction,
IconApps,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import Breadcrumbs from '../components/Breadcrumbs'; import Breadcrumbs from '../components/Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
interface App { interface App {
id: string; id: string;
@ -28,36 +30,71 @@ interface App {
category: string; category: string;
} }
const HomePage: React.FC = () => { // Map microfrontend IDs to display information
const navigate = useNavigate(); const getAppInfo = (id: string) => {
const [favoriteApps, setFavoriteApps] = useState<string[]>(['demo']); const appInfo: Record<string, { name: string; description: string; icon: React.ComponentType<any>; category: string }> = {
demo: {
const availableApps: App[] = [
{
id: 'demo',
name: 'Demo App', name: 'Demo App',
description: 'Sample microfrontend application for testing module federation', description: 'Sample microfrontend application for testing module federation',
icon: IconDashboard, icon: IconDashboard,
path: '/app/demo',
category: 'Development', category: 'Development',
}, },
{ kms: {
id: 'kms',
name: 'Key Management System', name: 'Key Management System',
description: 'Manage API keys, tokens, and access permissions for applications', description: 'Manage API keys, tokens, and access permissions for applications',
icon: IconKey, icon: IconKey,
path: '/app/kms',
category: 'Security', category: 'Security',
}, },
{ faas: {
id: 'faas',
name: 'Function-as-a-Service', name: 'Function-as-a-Service',
description: 'Deploy and manage serverless functions', description: 'Deploy and manage serverless functions',
icon: IconFunction, icon: IconFunction,
path: '/app/faas',
category: 'Development', 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) => { const toggleFavorite = (appId: string) => {
setFavoriteApps(prev => setFavoriteApps(prev =>

View File

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