UNPKG

create-bluecopa-react-app

Version:

CLI tool to create bluecopa React applications

361 lines (322 loc) 13.5 kB
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Bluecopa Preview</title> <meta name="description" content="Preview wrapper for federation module" /> <style> .loading { display: flex; align-items: center; justify-content: center; height: 200px; font-size: 16px; color: #666; } .error { padding: 20px; background: #fee; border: 1px solid #fcc; border-radius: 8px; margin: 20px; } .error h2 { color: #c33; margin-top: 0; } .error code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-family: monospace; } #bluecopa-preview { width: 100%; min-height: 500px; } </style> </head> <body> <div id="loading-indicator" class="loading"> Loading Bluecopa module... </div> <div id="error-container" style="display: none;"></div> <div id="bluecopa-preview"></div> <!-- SystemJS import map for shared dependencies (UMD for compatibility) --> <script type="systemjs-importmap"> { "imports": { "react": "https://unpkg.com/react@18/umd/react.development.js", "react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js", "react-router-dom": "https://unpkg.com/react-router-dom@6/dist/umd/react-router-dom.development.js", "single-spa": "https://unpkg.com/single-spa@5/dist/system/single-spa.min.js", "single-spa-react": "https://unpkg.com/single-spa-react@4/dist/umd/single-spa-react.min.js" } } </script> <!-- SystemJS for loading System format bluecopa modules --> <script src="https://unpkg.com/systemjs@6/dist/system.min.js"></script> <script type="module"> // Configure SystemJS with import map after load document.addEventListener('DOMContentLoaded', async () => { if (window.System) { const importMap = document.querySelector('script[type="systemjs-importmap"]'); if (importMap) { window.System.addImportMap(JSON.parse(importMap.textContent)); } // Auto-load on page open await loadFederationModule(); } }); let currentModule = null; let currentMountProps = null; // Helper to load remoteEntry.js script (no wait needed, as System.import will handle) async function loadRemoteEntryScript(moduleUrl) { return new Promise((resolve, reject) => { // Remove any previous script const existingScript = document.querySelector(`script[src='${moduleUrl}']`); if (existingScript) existingScript.remove(); // Create new script const script = document.createElement('script'); script.type = 'text/javascript'; script.src = moduleUrl; script.async = true; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load remoteEntry.js')); document.head.appendChild(script); }); } // Global function to load federation module window.loadFederationModule = async function() { // Hardcoded defaults const moduleUrl = "http://localhost:3001/assets/remoteEntry.js"; const moduleName = "__copa_ext_app__react_route"; const exposeName = "./App"; const basePath = "/preview"; const loadingIndicator = document.getElementById('loading-indicator'); const errorContainer = document.getElementById('error-container'); const previewContainer = document.getElementById('bluecopa-preview'); // Show loading state loadingIndicator.style.display = 'flex'; loadingIndicator.textContent = 'Loading bluecopa module...'; errorContainer.style.display = 'none'; previewContainer.innerHTML = ''; try { // Unload any existing module first await unloadModule(); console.log('Loading federation module from:', moduleUrl); // Load remoteEntry script await loadRemoteEntryScript(moduleUrl); // Clear System cache for fresh load if (window.System && typeof window.System.delete === 'function') { window.System.delete(moduleUrl); } // Use SystemJS to import the container with timeout if (typeof System !== 'undefined') { const container = await Promise.race([ System.import(moduleUrl), new Promise((_, reject) => setTimeout(() => reject(new Error('Module load timeout')), 15000)) ]); console.log('Loaded container:', container); // Try to get the exposed module with multiple entry points let appModule; if (typeof container.get === 'function') { const entryPoints = ['./App', './app', './index', './main', './src/App', './src/index']; for (const entry of entryPoints) { try { const factory = await container.get(entry); appModule = factory ? factory() : factory; console.log(`Found module at ${entry}:`, appModule); break; } catch (e) { console.log(`No module at ${entry}`); } } } if (!appModule) { appModule = container; } await mountModule(appModule, basePath, loadingIndicator, previewContainer, container); } else { throw new Error('SystemJS not available'); } } catch (error) { // Show error visibly on the page loadingIndicator.style.display = 'none'; errorContainer.style.display = 'block'; errorContainer.innerHTML = ` <div class="error"> <h2>Failed to Load Application</h2> <p>${error.message}</p> <ul> <li>Make sure the federation build is running: <code>npm run preview</code></li> <li>Verify the module URL points to the correct remoteEntry.js</li> <li>Check that module name matches your vite.config.ts federation name</li> <li>Open browser DevTools Console for detailed error information</li> <li>Ensure CORS is enabled on your federation server</li> </ul> <p>Refresh the page to retry.</p> </div> `; // Also log to console console.error('bluecopa preview error:', error); } }; // Helper function to mount the module async function mountModule(appModule, basePath, loadingIndicator, previewContainer, container) { // Create a container for the module const moduleContainer = document.createElement('div'); moduleContainer.id = 'single-spa-application:bluecopa-preview'; moduleContainer.style.width = '100%'; moduleContainer.style.minHeight = '500px'; previewContainer.appendChild(moduleContainer); console.log('App module:', appModule); console.log('App module type:', typeof appModule); let mounted = false; // Initialize container if needed if (typeof container.init === 'function') { try { await container.init({}); } catch (e) { console.warn('Container init failed:', e); } } // Try multiple mount patterns const mountFunc = appModule?.mount || appModule?.default?.mount || appModule?.render || appModule?.default?.render || appModule?.init || appModule?.default?.init || appModule?.bootstrap || appModule?.default?.bootstrap || appModule?.start || appModule?.default?.start || (typeof appModule === 'function' ? appModule : null); if (typeof mountFunc === 'function') { console.log('Found mount function, mounting app...'); // Clear container moduleContainer.innerHTML = ''; try { // Pattern 1: Single-spa config const props = { domElement: moduleContainer, basename: basePath, singleSpa: { name: 'bluecopa-preview', mountParcel: () => {}, getProps: () => ({}) } }; await mountFunc(props); mounted = true; } catch (e1) { console.log('Single-spa pattern failed:', e1); try { // Pattern 2: Direct element await mountFunc(moduleContainer); mounted = true; } catch (e2) { console.log('Direct element pattern failed:', e2); try { // Pattern 3: No args await mountFunc(); mounted = true; } catch (e3) { console.error('All mount patterns failed:', { e1, e2, e3 }); throw new Error('Failed to mount application with any pattern'); } } } if (mounted) { currentModule = { unmount: async () => await unmountModule(appModule, moduleContainer), container }; currentMountProps = { domElement: moduleContainer, basename: basePath }; loadingIndicator.style.display = 'none'; console.log('Bluecopa module mounted successfully'); return; } } // Fallback: Treat as plain React component if (typeof appModule === 'function') { console.log('Mounting as plain React component'); const { default: React } = await import('react'); const { createRoot } = await import('react-dom/client'); const root = createRoot(moduleContainer); root.render(React.createElement(appModule, { basename: basePath })); currentModule = { unmount: () => root.unmount(), container }; loadingIndicator.style.display = 'none'; console.log('React component mounted successfully'); return; } throw new Error('No valid mount function or React component found'); } // Helper to unmount async function unmountModule(appModule, moduleContainer) { // Try multiple unmount patterns const unmountFunc = appModule?.unmount || appModule?.default?.unmount || appModule?.destroy || appModule?.default?.destroy || appModule?.cleanup || appModule?.default?.cleanup || appModule?.dispose || appModule?.default?.dispose; if (typeof unmountFunc === 'function') { try { // Try with props if available if (currentMountProps) { await unmountFunc(currentMountProps); } else { await unmountFunc(); } console.log('Module unmounted via function'); return; } catch (e) { console.warn('Unmount function failed:', e); } } // Fallback manual cleanup if (moduleContainer) { moduleContainer.innerHTML = ''; } console.log('Module cleaned up manually'); } // Global function to unload module window.unloadModule = async function() { if (currentModule) { try { if (currentModule.container && typeof currentModule.container.delete === 'function') { currentModule.container.delete(currentModule.container.url || ''); // Clear cache } await unmountModule(currentModule.appModule || currentModule, document.getElementById('bluecopa-preview')); } catch (error) { console.error('Error unmounting module:', error); } } currentModule = null; currentMountProps = null; const previewContainer = document.getElementById('bluecopa-preview'); previewContainer.innerHTML = ''; const loadingIndicator = document.getElementById('loading-indicator'); loadingIndicator.style.display = 'flex'; loadingIndicator.textContent = 'Ready to load bluecopa module...'; const errorContainer = document.getElementById('error-container'); errorContainer.style.display = 'none'; }; // Global function to reload module window.reloadModule = async function() { await unloadModule(); // Clear any remaining cache if (window.System && typeof window.System.clear === 'function') { window.System.clear(); } // Small delay to ensure cleanup setTimeout(() => { loadFederationModule(); }, 100); }; </script> </body> </html>