UNPKG

@wizecorp/stratusjs

Version:
269 lines 9.33 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { renderToString } from 'react-dom/server'; import { ServiceProvider } from '../services/ServiceContext'; import { ServiceContainer } from '../services/ServiceContainer'; /** * Server-side renderer for Stratus applications */ export class SSRRenderer { constructor(config) { Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "routes", { enumerable: true, configurable: true, writable: true, value: [] }); this.config = { port: 3000, staticDir: 'public', template: this.getDefaultTemplate(), ...config }; } /** * Set routes for SSR */ setRoutes(routes) { this.routes = routes; } /** * Render a page server-side */ async renderPage(context) { try { // Find matching route const route = this.findMatchingRoute(context.pathname); if (!route) { return this.renderNotFound(context); } // Setup services for server-side if (this.config.setupServices) { await this.config.setupServices(context.services, context); } // Load server-side data if available let pageProps = {}; let redirect; let statusCode = 200; if (route.getServerSideProps) { const result = await route.getServerSideProps(context); if (result.redirect) { return { html: '', initialData: {}, statusCode: result.redirect.permanent ? 301 : 302, redirect: result.redirect.destination, headers: {} }; } if (result.notFound) { return this.renderNotFound(context); } pageProps = result.props || {}; } // Load component const componentModule = await route.component(); const Component = componentModule.default; // Prepare hydration data const hydrationData = { pathname: context.pathname, params: context.params, query: context.query, props: pageProps }; // Render component (simplified for SSR) const app = (_jsx(ServiceProvider, { container: context.services, children: _jsx(Component, { ...pageProps }) })); const html = renderToString(app); // Generate full HTML with template const fullHtml = this.generateHTML(html, hydrationData); return { html: fullHtml, initialData: hydrationData, statusCode, redirect, headers: {} }; } catch (error) { if (this.config.onError) { this.config.onError(error, context); } return this.renderError(error, context); } } /** * Generate static pages (for build time) */ async generateStaticPages() { const staticPages = []; for (const route of this.routes) { if (route.getStaticPaths && route.getStaticProps) { // Dynamic static pages const { paths } = await route.getStaticPaths(); for (const pathInfo of paths) { const mockContext = this.createMockContext(route.path, pathInfo.params); const result = await route.getStaticProps({ params: pathInfo.params }); if (!result.notFound && !result.redirect) { const componentModule = await route.component(); const Component = componentModule.default; const app = (_jsx(ServiceProvider, { container: new ServiceContainer(), children: _jsx(Component, { ...result.props }) })); const html = renderToString(app); const fullHtml = this.generateHTML(html, { pathname: mockContext.pathname, params: pathInfo.params, query: {}, props: result.props }); staticPages.push({ path: mockContext.pathname, html: fullHtml }); } } } else if (route.getStaticProps && !route.path.includes(':')) { // Static page without dynamic segments const result = await route.getStaticProps({ params: {} }); if (!result.notFound && !result.redirect) { const componentModule = await route.component(); const Component = componentModule.default; const app = (_jsx(ServiceProvider, { container: new ServiceContainer(), children: _jsx(Component, { ...result.props }) })); const html = renderToString(app); const fullHtml = this.generateHTML(html, { pathname: route.path, params: {}, query: {}, props: result.props }); staticPages.push({ path: route.path, html: fullHtml }); } } } return staticPages; } /** * Find matching route for a pathname */ findMatchingRoute(pathname) { return this.routes.find(route => { // Simple pattern matching - can be enhanced if (route.path === pathname) return true; // Handle dynamic routes const routeSegments = route.path.split('/'); const pathSegments = pathname.split('/'); if (routeSegments.length !== pathSegments.length) return false; return routeSegments.every((segment, index) => { return segment.startsWith(':') || segment.startsWith('*') || segment === pathSegments[index]; }); }); } /** * Generate full HTML document */ generateHTML(content, hydrationData) { const hydrationScript = ` <script> window.__STRATUS_DATA__ = ${JSON.stringify(hydrationData).replace(/</g, '\\u003c')}; </script> `; if (this.config.document) { // Use custom document component const Document = this.config.document; const documentHtml = renderToString(_jsx(Document, { html: content, initialData: hydrationData })); return `<!DOCTYPE html>${documentHtml}${hydrationScript}`; } // Use default template return this.config.template .replace('{{content}}', content) .replace('{{hydration}}', hydrationScript); } /** * Render 404 page */ renderNotFound(context) { const html = this.generateHTML('<div><h1>404 - Page Not Found</h1></div>', { pathname: context.pathname, params: {}, query: {}, props: {} }); return { html, initialData: {}, statusCode: 404, headers: {} }; } /** * Render error page */ renderError(error, context) { const html = this.generateHTML(`<div><h1>500 - Server Error</h1><p>${error.message}</p></div>`, { pathname: context.pathname, params: {}, query: {}, props: {} }); return { html, initialData: {}, statusCode: 500, headers: {} }; } /** * Create mock context for static generation */ createMockContext(path, params) { return { request: { url: path, method: 'GET', headers: {}, cookies: {} }, response: { status: 200, headers: {} }, pathname: path, params, query: {}, services: new ServiceContainer(), data: {} }; } /** * Default HTML template */ getDefaultTemplate() { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Stratus App</title> </head> <body> <div id="root">{{content}}</div> {{hydration}} <script src="/bundle.js"></script> </body> </html> `; } } //# sourceMappingURL=SSRRenderer.js.map