Debugging and troubleshooting Module Federation is an important part of the development process. Here is a detailed debugging solution:
1. Development Environment Debugging
Enable Source Maps:
javascript// webpack.config.js module.exports = { devtool: 'source-map', output: { devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]?[loaders]', devtoolFallbackModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]?[loaders]' } }
Configure Development Server:
javascript// webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { devServer: { port: 3000, hot: true, historyApiFallback: true, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization' }, client: { overlay: { errors: true, warnings: false } } }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ] }
2. Module Loading Debugging
Module Loading Tracking:
javascript// module-loader-debug.js class ModuleLoaderDebugger { constructor() { this.loadTimes = new Map() this.loadErrors = new Map() this.interceptImports() } interceptImports() { const originalImport = window.__webpack_require__ window.__webpack_require__ = (moduleId) => { const startTime = performance.now() try { const module = originalImport(moduleId) const loadTime = performance.now() - startTime this.loadTimes.set(moduleId, { loadTime, timestamp: Date.now() }) console.log(`✅ Module loaded: ${moduleId} (${loadTime.toFixed(2)}ms)`) return module } catch (error) { this.loadErrors.set(moduleId, { error, timestamp: Date.now() }) console.error(`❌ Module load failed: ${moduleId}`, error) throw error } } } getLoadStats() { return { totalModules: this.loadTimes.size, totalErrors: this.loadErrors.size, averageLoadTime: Array.from(this.loadTimes.values()) .reduce((sum, stat) => sum + stat.loadTime, 0) / this.loadTimes.size, slowModules: Array.from(this.loadTimes.entries()) .filter(([_, stat]) => stat.loadTime > 1000) .map(([id, stat]) => ({ id, loadTime: stat.loadTime })) } } } // Initialize debugger if (process.env.NODE_ENV === 'development') { new ModuleLoaderDebugger() }
3. Remote Module Debugging
Remote Module Inspection Tool:
javascript// remote-module-debugger.js class RemoteModuleDebugger { constructor() { this.remoteModules = new Map() this.checkRemoteModules() } async checkRemoteModules() { const remotes = this.getRemoteConfig() for (const [name, config] of Object.entries(remotes)) { try { await this.checkRemoteModule(name, config) } catch (error) { console.error(`Remote module check failed: ${name}`, error) } } } getRemoteConfig() { // Get remote module configuration from webpack config return { remoteApp1: { url: 'http://localhost:3001/remoteEntry.js', scope: 'remoteApp1' }, remoteApp2: { url: 'http://localhost:3002/remoteEntry.js', scope: 'remoteApp2' } } } async checkRemoteModule(name, config) { console.log(`Checking remote module: ${name}`) // Check if remote entry file is accessible const response = await fetch(config.url) if (!response.ok) { throw new Error(`Failed to fetch remote entry: ${response.status}`) } // Check entry file content const content = await response.text() if (!content.includes(config.scope)) { throw new Error(`Remote entry does not contain scope: ${config.scope}`) } // Try to load remote module try { const module = await import(`${name}/Module`) this.remoteModules.set(name, { status: 'loaded', module, timestamp: Date.now() }) console.log(`✅ Remote module loaded: ${name}`) } catch (error) { this.remoteModules.set(name, { status: 'error', error, timestamp: Date.now() }) console.error(`❌ Failed to load remote module: ${name}`, error) } } getModuleStatus(name) { return this.remoteModules.get(name) } getAllModuleStatus() { return Object.fromEntries(this.remoteModules) } } export const remoteModuleDebugger = new RemoteModuleDebugger()
4. Shared Dependency Debugging
Shared Dependency Inspection Tool:
javascript// shared-dependency-debugger.js class SharedDependencyDebugger { constructor() { this.sharedDependencies = new Map() this.checkSharedDependencies() } checkSharedDependencies() { const shared = this.getSharedConfig() for (const [name, config] of Object.entries(shared)) { this.checkSharedDependency(name, config) } } getSharedConfig() { // Get shared dependency configuration from webpack config return { react: { singleton: true, requiredVersion: '^17.0.0', strictVersion: false }, 'react-dom': { singleton: true, requiredVersion: '^17.0.0', strictVersion: false } } } checkSharedDependency(name, config) { try { const dependency = require(name) const version = dependency.version const isSingleton = config.singleton const requiredVersion = config.requiredVersion const strictVersion = config.strictVersion const isCompatible = this.checkVersionCompatibility( version, requiredVersion, strictVersion ) this.sharedDependencies.set(name, { version, isSingleton, requiredVersion, isCompatible, status: isCompatible ? 'ok' : 'incompatible' }) if (isCompatible) { console.log(`✅ Shared dependency: ${name}@${version}`) } else { console.warn(`⚠️ Incompatible shared dependency: ${name}@${version} (required: ${requiredVersion})`) } } catch (error) { this.sharedDependencies.set(name, { status: 'error', error }) console.error(`❌ Failed to load shared dependency: ${name}`, error) } } checkVersionCompatibility(currentVersion, requiredVersion, strictVersion) { if (strictVersion) { return currentVersion === requiredVersion } // Use semver to check version compatibility const semver = require('semver') return semver.satisfies(currentVersion, requiredVersion) } getDependencyStatus(name) { return this.sharedDependencies.get(name) } getAllDependencyStatus() { return Object.fromEntries(this.sharedDependencies) } } export const sharedDependencyDebugger = new SharedDependencyDebugger()
5. Performance Analysis Tools
Module Loading Performance Analysis:
javascript// performance-analyzer.js class PerformanceAnalyzer { constructor() { this.metrics = new Map() this.setupPerformanceObserver() } setupPerformanceObserver() { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { if (entry.entryType === 'resource') { this.recordResourceLoad(entry) } }) }) observer.observe({ entryTypes: ['resource', 'measure'] }) } } recordResourceLoad(entry) { const { name, duration, transferSize } = entry if (name.includes('remoteEntry') || name.includes('node_modules')) { this.metrics.set(name, { duration, transferSize, timestamp: entry.startTime }) } } analyzeModuleLoad(moduleName) { const startTime = performance.now() return { start: () => startTime, end: () => { const endTime = performance.now() const duration = endTime - startTime this.metrics.set(moduleName, { duration, timestamp: startTime }) console.log(`📊 Module load time: ${moduleName} (${duration.toFixed(2)}ms)`) return duration } } } getPerformanceReport() { const metrics = Array.from(this.metrics.values()) return { totalLoadTime: metrics.reduce((sum, m) => sum + (m.duration || 0), 0), totalTransferSize: metrics.reduce((sum, m) => sum + (m.transferSize || 0), 0), averageLoadTime: metrics.reduce((sum, m) => sum + (m.duration || 0), 0) / metrics.length, slowResources: metrics.filter(m => m.duration > 1000), largeResources: metrics.filter(m => m.transferSize > 100000) } } } export const performanceAnalyzer = new PerformanceAnalyzer()
6. Error Handling and Logging
Global Error Handling:
javascript// error-handler.js class ErrorHandler { constructor() { this.errors = [] this.setupErrorHandlers() } setupErrorHandlers() { // Capture module loading errors window.addEventListener('error', (event) => { if (event.filename && event.filename.includes('remoteEntry')) { this.handleModuleLoadError(event) } }) // Capture unhandled Promise rejections window.addEventListener('unhandledrejection', (event) => { this.handleUnhandledRejection(event) }) } handleModuleLoadError(event) { const error = { type: 'module_load_error', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack, timestamp: Date.now() } this.errors.push(error) console.error('Module load error:', error) // Send to error tracking service this.sendToErrorTracking(error) } handleUnhandledRejection(event) { const error = { type: 'unhandled_rejection', message: event.reason?.message || String(event.reason), stack: event.reason?.stack, timestamp: Date.now() } this.errors.push(error) console.error('Unhandled rejection:', error) // Send to error tracking service this.sendToErrorTracking(error) } sendToErrorTracking(error) { // Send to Sentry or other error tracking service if (window.Sentry) { window.Sentry.captureException(error) } // Or send to custom error tracking endpoint fetch('/api/error-tracking', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(error) }).catch(console.error) } getErrors() { return this.errors } clearErrors() { this.errors = [] } } export const errorHandler = new ErrorHandler()
7. Debug Tool Integration
React DevTools Integration:
javascript// debug-tools.js import React from 'react' import { createRoot } from 'react-dom/client' // Development environment debug panel if (process.env.NODE_ENV === 'development') { const DebugPanel = () => { const [stats, setStats] = React.useState({}) React.useEffect(() => { const interval = setInterval(() => { setStats({ modules: window.__webpack_require__.c, loadTimes: window.moduleLoadTimes, remoteModules: window.remoteModules }) }, 1000) return () => clearInterval(interval) }, []) return React.createElement('div', { style: { position: 'fixed', bottom: 0, right: 0, background: 'rgba(0,0,0,0.8)', color: 'white', padding: '10px', fontSize: '12px', zIndex: 9999 } }, React.createElement('pre', null, JSON.stringify(stats, null, 2))) } const root = createRoot(document.createElement('div')) root.render(React.createElement(DebugPanel)) }
Through these debugging tools and methods, you can effectively troubleshoot and resolve various issues encountered during Module Federation development.