UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

609 lines (544 loc) 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReactTemplate = void 0; const index_1 = require("../index"); class ReactTemplate extends index_1.BaseTemplate { constructor(framework, context) { super(framework, context); } async generateFiles() { const files = []; const { hasTypeScript } = this.context; const ext = hasTypeScript ? 'tsx' : 'jsx'; const configExt = hasTypeScript ? 'ts' : 'js'; // Package.json files.push({ path: 'package.json', content: JSON.stringify(this.generatePackageJson(), null, 2) }); // Vite config files.push({ path: `vite.config.${configExt}`, content: this.generateViteConfig() }); // TypeScript config (if TypeScript) if (hasTypeScript) { files.push({ path: 'tsconfig.json', content: this.generateTsConfig() }); } // ESLint config files.push({ path: '.eslintrc.js', content: this.generateEslintConfig() }); // Main entry file files.push({ path: `src/index.${ext}`, content: this.generateIndexFile() }); // App component files.push({ path: `src/App.${ext}`, content: this.generateAppComponent() }); // Event bus (if needed) files.push({ path: `src/eventBus.${hasTypeScript ? 'ts' : 'js'}`, content: this.generateEventBus() }); // HTML file for development files.push({ path: 'public/index.html', content: this.generateHtmlFile() }); // README files.push({ path: 'README.md', content: this.generateReadme() }); // CSS file files.push({ path: 'src/App.css', content: this.generateCssFile() }); // Dockerfile for containerization files.push({ path: 'Dockerfile', content: this.generateDockerfile() }); // Docker Compose for development files.push({ path: 'docker-compose.yml', content: this.generateDockerCompose() }); // Docker ignore files.push({ path: '.dockerignore', content: this.generateDockerIgnore() }); // Environment configuration files.push({ path: '.env.example', content: this.generateEnvExample() }); // Nginx configuration for production files.push({ path: 'nginx.conf', content: this.generateNginxConfig() }); return files; } generateIndexFile() { const { normalizedName, hasTypeScript } = this.context; const componentName = normalizedName.charAt(0).toUpperCase() + normalizedName.slice(1).replace(/-./g, x => x[1].toUpperCase()); if (hasTypeScript) { return `import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import App from './App'; import { eventBus } from './eventBus'; // Extend window interface for TypeScript declare global { interface Window { ${componentName}: { mount: (containerId: string) => void; unmount: () => void; root?: Root; }; } } // Entry point for the microfrontend // This gets exposed when the script is loaded window.${componentName} = { mount: (containerId: string) => { const container = document.getElementById(containerId); if (!container) { console.error(\`Container element with ID "\${containerId}" not found\`); return; } // Using React 18's createRoot API const root = createRoot(container); root.render(<App />); // Notify shell that microfrontend is loaded eventBus.emit('microfrontend:loaded', { id: '${normalizedName}' }); // Store root for unmounting window.${componentName}.root = root; }, unmount: () => { if (window.${componentName}.root) { window.${componentName}.root.unmount(); eventBus.emit('microfrontend:unloaded', { id: '${normalizedName}' }); } } }; // For development mode if (process.env.NODE_ENV === 'development') { const devContainer = document.getElementById('root'); if (devContainer) { window.${componentName}.mount('root'); } } export default App;`; } else { return `import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; import { eventBus } from './eventBus'; // Entry point for the microfrontend // This gets exposed when the script is loaded window.${componentName} = { mount: (containerId) => { const container = document.getElementById(containerId); if (!container) { console.error(\`Container element with ID "\${containerId}" not found\`); return; } // Using React 18's createRoot API const root = createRoot(container); root.render(<App />); // Notify shell that microfrontend is loaded eventBus.emit('microfrontend:loaded', { id: '${normalizedName}' }); // Store root for unmounting window.${componentName}.root = root; }, unmount: () => { if (window.${componentName}.root) { window.${componentName}.root.unmount(); eventBus.emit('microfrontend:unloaded', { id: '${normalizedName}' }); } } }; // For development mode if (process.env.NODE_ENV === 'development') { const devContainer = document.getElementById('root'); if (devContainer) { window.${componentName}.mount('root'); } } export default App;`; } } generateAppComponent() { const { normalizedName, hasTypeScript, name } = this.context; if (hasTypeScript) { return `import React from 'react'; import './App.css'; interface AppProps {} function App(props: AppProps): JSX.Element { return ( <div className="${normalizedName}-app"> <header className="${normalizedName}-header"> <h1>${name}</h1> <p>A React microfrontend built with Re-Shell CLI</p> </header> <main className="${normalizedName}-main"> <section className="${normalizedName}-content"> <h2>Welcome to ${name}</h2> <p>This microfrontend is ready for development!</p> <div className="${normalizedName}-features"> <div className="feature-card"> <h3>🚀 Fast Development</h3> <p>Hot module replacement and fast refresh</p> </div> <div className="feature-card"> <h3>📦 Modular Architecture</h3> <p>Independent deployment and development</p> </div> <div className="feature-card"> <h3>🔧 TypeScript Support</h3> <p>Type-safe development experience</p> </div> </div> </section> </main> </div> ); } export default App;`; } else { return `import React from 'react'; import './App.css'; function App() { return ( <div className="${normalizedName}-app"> <header className="${normalizedName}-header"> <h1>${name}</h1> <p>A React microfrontend built with Re-Shell CLI</p> </header> <main className="${normalizedName}-main"> <section className="${normalizedName}-content"> <h2>Welcome to ${name}</h2> <p>This microfrontend is ready for development!</p> <div className="${normalizedName}-features"> <div className="feature-card"> <h3>🚀 Fast Development</h3> <p>Hot module replacement and fast refresh</p> </div> <div className="feature-card"> <h3>📦 Modular Architecture</h3> <p>Independent deployment and development</p> </div> <div className="feature-card"> <h3>⚡ Modern Tooling</h3> <p>Vite-powered build system</p> </div> </div> </section> </main> </div> ); } export default App;`; } } generateEventBus() { const { hasTypeScript } = this.context; if (hasTypeScript) { return `// Simple event bus for microfrontend communication interface EventBusEvents { 'microfrontend:loaded': { id: string }; 'microfrontend:unloaded': { id: string }; [key: string]: any; } type EventCallback<T = any> = (data: T) => void; class EventBus { private events: Map<string, EventCallback[]> = new Map(); emit<K extends keyof EventBusEvents>(event: K, data: EventBusEvents[K]): void { const callbacks = this.events.get(event as string); if (callbacks) { callbacks.forEach(callback => callback(data)); } } on<K extends keyof EventBusEvents>(event: K, callback: EventCallback<EventBusEvents[K]>): void { if (!this.events.has(event as string)) { this.events.set(event as string, []); } this.events.get(event as string)!.push(callback); } off<K extends keyof EventBusEvents>(event: K, callback: EventCallback<EventBusEvents[K]>): void { const callbacks = this.events.get(event as string); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } } } } export const eventBus = new EventBus();`; } else { return `// Simple event bus for microfrontend communication class EventBus { constructor() { this.events = new Map(); } emit(event, data) { const callbacks = this.events.get(event); if (callbacks) { callbacks.forEach(callback => callback(data)); } } on(event, callback) { if (!this.events.has(event)) { this.events.set(event, []); } this.events.get(event).push(callback); } off(event, callback) { const callbacks = this.events.get(event); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } } } } export const eventBus = new EventBus();`; } } generateHtmlFile() { const { name } = this.context; return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${name} - Re-Shell Microfrontend</title> </head> <body> <div id="root"></div> <script type="module" src="/src/index.${this.context.hasTypeScript ? 'tsx' : 'jsx'}"></script> </body> </html>`; } generateCssFile() { const { normalizedName } = this.context; return `.${normalizedName}-app { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; padding: 20px; max-width: 1200px; margin: 0 auto; } .${normalizedName}-header { text-align: center; margin-bottom: 40px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; } .${normalizedName}-header h1 { margin: 0 0 10px 0; font-size: 2.5rem; font-weight: 600; } .${normalizedName}-header p { margin: 0; font-size: 1.1rem; opacity: 0.9; } .${normalizedName}-main { margin-top: 20px; } .${normalizedName}-content h2 { color: #333; margin-bottom: 20px; font-size: 2rem; text-align: center; } .${normalizedName}-content > p { text-align: center; font-size: 1.1rem; color: #666; margin-bottom: 40px; } .${normalizedName}-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 30px; } .feature-card { background: #f8f9fa; padding: 30px; border-radius: 8px; border: 1px solid #e9ecef; transition: transform 0.2s ease, box-shadow 0.2s ease; } .feature-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .feature-card h3 { margin: 0 0 15px 0; color: #495057; font-size: 1.3rem; } .feature-card p { margin: 0; color: #6c757d; line-height: 1.6; } @media (max-width: 768px) { .${normalizedName}-app { padding: 10px; } .${normalizedName}-header h1 { font-size: 2rem; } .${normalizedName}-features { grid-template-columns: 1fr; } }`; } generateDockerfile() { return `# Multi-stage build for React microfrontend FROM node:18-alpine as build WORKDIR /app # Copy package files COPY package*.json ./ RUN npm ci --only=production # Copy source code COPY . . # Build the application RUN npm run build # Production stage with nginx FROM nginx:alpine # Copy built assets COPY --from=build /app/dist /usr/share/nginx/html # Copy nginx configuration COPY nginx.conf /etc/nginx/conf.d/default.conf # Expose port EXPOSE 80 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost/health || exit 1 CMD ["nginx", "-g", "daemon off;"]`; } generateDockerCompose() { const { normalizedName, name } = this.context; const port = 3000; return `services: ${normalizedName}: build: . ports: - "${port}:${port}" environment: - NODE_ENV=development - VITE_API_URL=http://localhost:8000 - VITE_APP_NAME=${name} volumes: - .:/app - /app/node_modules command: npm run dev -- --host 0.0.0.0 --port ${port} healthcheck: test: ["CMD", "curl", "-f", "http://localhost:${port}"] interval: 30s timeout: 10s retries: 3 start_period: 40s`; } generateDockerIgnore() { return `node_modules npm-debug.log* yarn-debug.log* yarn-error.log* .git .gitignore README.md .env .nyc_output coverage .idea .vscode .DS_Store dist build`; } generateEnvExample() { const { normalizedName, name } = this.context; return `# Application Configuration VITE_APP_NAME=${name} VITE_APP_VERSION=1.0.0 # API Configuration VITE_API_URL=http://localhost:8000 VITE_API_TIMEOUT=5000 # Microfrontend Configuration VITE_MF_ID=${normalizedName} VITE_MF_PORT=3000 # Environment NODE_ENV=development # Feature Flags VITE_ENABLE_ANALYTICS=false VITE_ENABLE_MONITORING=false`; } generateNginxConfig() { return `server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied expired no-cache no-store private must-revalidate auth; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # Handle SPA routing location / { try_files $uri $uri/ /index.html; } # Static assets caching location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Health check endpoint location /health { access_log off; return 200 "healthy\\n"; add_header Content-Type text/plain; } # Disable access to hidden files location ~ /\\. { deny all; } }`; } } exports.ReactTemplate = ReactTemplate;