As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
JavaScript Patterns for Building Micro-Frontends
Micro-frontend architecture has transformed how we build large-scale web applications. I've implemented these patterns across various projects, and they've consistently proven effective for creating maintainable, scalable applications.
Application Shell Pattern
The application shell serves as the foundation of micro-frontend architecture. It handles core functionalities while keeping the main application lightweight and performant.
// app-shell.js
class AppShell {
constructor() {
this.routes = new Map();
this.auth = new AuthService();
this.container = document.getElementById('root');
}
registerRoute(path, microFrontend) {
this.routes.set(path, microFrontend);
}
async loadMicroFrontend(path) {
const mfe = this.routes.get(path);
if (!mfe) return;
await mfe.mount(this.container);
}
}
const shell = new AppShell();
shell.registerRoute('/dashboard', DashboardMFE);
shell.registerRoute('/profile', ProfileMFE);
Module Federation Implementation
Module Federation enables dynamic code sharing between applications. This pattern significantly reduces duplicate code and ensures consistent behavior across micro-frontends.
// webpack.config.js
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./DashboardWidget': './src/components/DashboardWidget'
},
shared: ['react', 'react-dom']
})
]
};
// Consumer application
import('dashboard/DashboardWidget').then(module => {
const DashboardWidget = module.default;
// Use the component
});
Event Communication System
A robust event communication system ensures smooth interaction between micro-frontends while maintaining loose coupling.
class EventBus {
constructor() {
this.events = {};
}
publish(event, data) {
const customEvent = new CustomEvent(event, { detail: data });
window.dispatchEvent(customEvent);
}
subscribe(event, callback) {
const handler = (e) => callback(e.detail);
window.addEventListener(event, handler);
return () => window.removeEventListener(event, handler);
}
}
const eventBus = new EventBus();
// Usage in micro-frontends
eventBus.publish('user-updated', { name: 'John Doe' });
eventBus.subscribe('user-updated', (data) => console.log(data));
Shared State Management
Managing state across micro-frontends requires a balanced approach between isolation and sharing.
class SharedStateManager {
constructor() {
this.state = {};
this.listeners = new Set();
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notifyListeners();
}
getState() {
return this.state;
}
subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
notifyListeners() {
this.listeners.forEach(listener => listener(this.state));
}
}
const stateManager = new SharedStateManager();
// Usage in micro-frontends
stateManager.setState({ theme: 'dark' });
stateManager.subscribe(state => console.log('State updated:', state));
Asset Loading Strategies
Efficient asset loading is crucial for micro-frontend performance. I've developed techniques to optimize resource loading and improve user experience.
class AssetLoader {
constructor() {
this.loaded = new Set();
}
async loadScript(url) {
if (this.loaded.has(url)) return;
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => {
this.loaded.add(url);
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
}
async loadStyles(url) {
if (this.loaded.has(url)) return;
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
this.loaded.add(url);
}
}
const loader = new AssetLoader();
await loader.loadScript('https://mfe.com/dashboard.js');
Error Boundary Implementation
Robust error handling prevents cascading failures across micro-frontends. Here's an implementation that isolates errors effectively.
class MicroFrontendErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Micro-frontend error:', error);
// Report to error tracking service
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>Something went wrong in this micro-frontend.</h2>
<button onClick={() => this.setState({ hasError: false })}>
Retry
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
<MicroFrontendErrorBoundary>
<MicroFrontendComponent />
</MicroFrontendErrorBoundary>
Integration Example
Here's how these patterns work together in a real-world scenario:
class MicroFrontendOrchestrator {
constructor() {
this.shell = new AppShell();
this.eventBus = new EventBus();
this.stateManager = new SharedStateManager();
this.assetLoader = new AssetLoader();
}
async bootstrapMicroFrontend(name, config) {
try {
await this.assetLoader.loadScript(config.entry);
const mfe = window[name];
mfe.initialize({
eventBus: this.eventBus,
stateManager: this.stateManager
});
this.shell.registerRoute(config.route, mfe);
} catch (error) {
console.error(`Failed to bootstrap ${name}:`, error);
}
}
}
const orchestrator = new MicroFrontendOrchestrator();
orchestrator.bootstrapMicroFrontend('dashboard', {
entry: 'https://mfe.com/dashboard.js',
route: '/dashboard'
});
These patterns form a comprehensive foundation for building scalable micro-frontend applications. The key is maintaining flexibility while ensuring reliable communication and state management between components. Implementation details may vary based on specific requirements, but these core patterns remain consistent across successful micro-frontend architectures.
Regular testing and monitoring are essential to maintain the health of micro-frontend applications. Using these patterns, teams can work independently while maintaining a cohesive user experience. The modular nature of this architecture allows for gradual adoption and easy updates, making it particularly valuable for large-scale applications.
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva