frontend-hamroun 1.2.77 → 1.2.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/batch/package.json +1 -1
  2. package/dist/client-router/package.json +1 -1
  3. package/dist/component/package.json +1 -1
  4. package/dist/context/package.json +1 -1
  5. package/dist/event-bus/package.json +1 -1
  6. package/dist/forms/package.json +1 -1
  7. package/dist/hooks/package.json +1 -1
  8. package/dist/index.mjs +1 -0
  9. package/dist/jsx-runtime/package.json +1 -1
  10. package/dist/lifecycle-events/package.json +1 -1
  11. package/dist/package.json +1 -1
  12. package/dist/render-component/package.json +1 -1
  13. package/dist/renderer/package.json +1 -1
  14. package/dist/router/package.json +1 -1
  15. package/dist/server/package.json +1 -1
  16. package/dist/server/src/index.js +24 -23
  17. package/dist/server/src/index.js.map +1 -1
  18. package/dist/server/src/renderComponent.d.ts +8 -9
  19. package/dist/server/src/renderComponent.js +10 -5
  20. package/dist/server/src/renderComponent.js.map +1 -1
  21. package/dist/server/src/server/index.d.ts +23 -34
  22. package/dist/server/src/server/index.js +170 -50
  23. package/dist/server/src/server/index.js.map +1 -1
  24. package/dist/server/src/server/templates.d.ts +2 -0
  25. package/dist/server/src/server/templates.js +9 -5
  26. package/dist/server/src/server/templates.js.map +1 -1
  27. package/dist/server/src/server/utils.d.ts +1 -1
  28. package/dist/server/src/server/utils.js +1 -1
  29. package/dist/server/src/server/utils.js.map +1 -1
  30. package/dist/server/tsconfig.server.tsbuildinfo +1 -1
  31. package/dist/server-renderer/package.json +1 -1
  32. package/dist/store/package.json +1 -1
  33. package/dist/types/package.json +1 -1
  34. package/dist/utils/package.json +1 -1
  35. package/dist/vdom/package.json +1 -1
  36. package/dist/wasm/package.json +1 -1
  37. package/package.json +1 -1
  38. package/templates/complete-app/client.js +58 -0
  39. package/templates/complete-app/package-lock.json +2536 -0
  40. package/templates/complete-app/package.json +8 -31
  41. package/templates/complete-app/pages/about.js +119 -0
  42. package/templates/complete-app/pages/index.js +157 -0
  43. package/templates/complete-app/pages/wasm-demo.js +290 -0
  44. package/templates/complete-app/public/client.js +80 -0
  45. package/templates/complete-app/public/index.html +47 -0
  46. package/templates/complete-app/public/styles.css +446 -212
  47. package/templates/complete-app/readme.md +188 -0
  48. package/templates/complete-app/server.js +417 -0
  49. package/templates/complete-app/server.ts +275 -0
  50. package/templates/complete-app/src/App.tsx +59 -0
  51. package/templates/complete-app/src/client.ts +61 -0
  52. package/templates/complete-app/src/client.tsx +18 -0
  53. package/templates/complete-app/src/pages/index.tsx +51 -0
  54. package/templates/complete-app/src/server.ts +218 -0
  55. package/templates/complete-app/tsconfig.json +22 -0
  56. package/templates/complete-app/tsconfig.server.json +19 -0
  57. package/templates/complete-app/vite.config.js +57 -0
  58. package/templates/complete-app/vite.config.ts +30 -0
  59. package/templates/go/example.go +154 -99
  60. package/templates/complete-app/build.js +0 -284
  61. package/templates/complete-app/src/api/index.js +0 -31
  62. package/templates/complete-app/src/client.js +0 -93
  63. package/templates/complete-app/src/components/App.js +0 -66
  64. package/templates/complete-app/src/components/Footer.js +0 -19
  65. package/templates/complete-app/src/components/Header.js +0 -38
  66. package/templates/complete-app/src/pages/About.js +0 -59
  67. package/templates/complete-app/src/pages/Home.js +0 -54
  68. package/templates/complete-app/src/pages/WasmDemo.js +0 -136
  69. package/templates/complete-app/src/server.js +0 -186
  70. package/templates/complete-app/src/wasm/build.bat +0 -16
  71. package/templates/complete-app/src/wasm/build.sh +0 -16
  72. package/templates/complete-app/src/wasm/example.go +0 -101
@@ -0,0 +1,275 @@
1
+ import express from 'express';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import { renderToString } from 'frontend-hamroun';
5
+ import { Database } from 'frontend-hamroun';
6
+ import { AuthService } from 'frontend-hamroun';
7
+ import { requestLogger, errorHandler, notFoundHandler, rateLimit } from 'frontend-hamroun';
8
+ import dotenv from 'dotenv';
9
+ import fetch from 'node-fetch';
10
+
11
+ // Load environment variables
12
+ dotenv.config();
13
+
14
+ // Get directory name in ESM
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ // Create Express app
19
+ const app = express();
20
+ const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
21
+
22
+ // Add middleware
23
+ app.use(express.json());
24
+ app.use(express.urlencoded({ extended: true }));
25
+ app.use(requestLogger);
26
+
27
+ // Rate limiting for API routes
28
+ app.use('/api', rateLimit({
29
+ windowMs: 15 * 60 * 1000, // 15 minutes
30
+ max: 100 // limit each IP to 100 requests per windowMs
31
+ }));
32
+
33
+ // Configure database if connection string is provided
34
+ let db = null;
35
+ if (process.env.DATABASE_URL) {
36
+ db = new Database({
37
+ url: process.env.DATABASE_URL,
38
+ type: (process.env.DATABASE_TYPE || 'mongodb') as 'mongodb' | 'mysql' | 'postgres'
39
+ });
40
+
41
+ // Connect to database
42
+ try {
43
+ await db.connect();
44
+ console.log('Database connected successfully');
45
+ } catch (error) {
46
+ console.error('Database connection failed:', error);
47
+ }
48
+ }
49
+
50
+ // Configure auth if secret is provided
51
+ let auth = null;
52
+ if (process.env.JWT_SECRET) {
53
+ auth = new AuthService({
54
+ secret: process.env.JWT_SECRET,
55
+ expiresIn: process.env.JWT_EXPIRES_IN || '24h'
56
+ });
57
+
58
+ // Add auth middleware
59
+ app.use(auth.initialize());
60
+
61
+ // Example protected route
62
+ app.get('/api/protected', auth.requireAuth(), (req, res) => {
63
+ res.json({ message: 'Protected route accessed successfully' });
64
+ });
65
+
66
+ // Example role-based protection
67
+ app.get('/api/admin', auth.requireRoles(['admin']), (req, res) => {
68
+ res.json({ message: 'Admin route accessed successfully' });
69
+ });
70
+
71
+ // Login route
72
+ app.post('/api/login', async (req, res) => {
73
+ const { username, password } = req.body;
74
+
75
+ // In a real app, fetch user from database
76
+ const user = { id: 1, username, roles: ['user'] };
77
+ const token = auth.generateToken(user);
78
+
79
+ res.json({ token, user: { id: user.id, username: user.username, roles: user.roles } });
80
+ });
81
+ }
82
+
83
+ // Serve static files from public directory
84
+ app.use(express.static(join(__dirname, 'public')));
85
+
86
+ // API endpoint example
87
+ app.get('/api/page-data', (req, res) => {
88
+ res.json({
89
+ title: 'Server-side Data',
90
+ content: 'This data was fetched from the server',
91
+ timestamp: new Date().toISOString()
92
+ });
93
+ });
94
+
95
+ // Meta tag generation function (using local logic for simplicity)
96
+ async function generateMetaTags(pageContent) {
97
+ // Extract title from page content
98
+ const title = pageContent.split('\n')[0].replace(/[#*]/g, '').trim() ||
99
+ 'Frontend Hamroun SSR Page';
100
+
101
+ // Generate description from content
102
+ const description = pageContent.substring(0, 150) + '...';
103
+
104
+ // Extract keywords
105
+ const keywords = pageContent
106
+ .toLowerCase()
107
+ .replace(/[^\w\s]/g, '')
108
+ .split(/\s+/)
109
+ .filter(w => w.length > 3)
110
+ .slice(0, 5)
111
+ .join(', ');
112
+
113
+ return {
114
+ title,
115
+ description,
116
+ keywords
117
+ };
118
+ }
119
+
120
+ // Helper function to check if file exists
121
+ async function fileExists(path) {
122
+ try {
123
+ const fs = await import('fs/promises');
124
+ await fs.access(path);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ // Implement basic SSR without relying on complex server functionality
132
+ app.get('*', async (req, res) => {
133
+ try {
134
+ // Import the page component
135
+ const pagesDir = join(__dirname, 'src', 'pages');
136
+ let componentPath;
137
+
138
+ // Map URL path to component file
139
+ if (req.path === '/') {
140
+ componentPath = join(pagesDir, 'index.js');
141
+ } else {
142
+ componentPath = join(pagesDir, `${req.path}.js`);
143
+ // Check if it's a directory with index.js
144
+ if (!await fileExists(componentPath)) {
145
+ componentPath = join(pagesDir, req.path, 'index.js');
146
+ }
147
+ }
148
+
149
+ // If component doesn't exist, return 404
150
+ if (!await fileExists(componentPath)) {
151
+ return res.status(404).send(`
152
+ <!DOCTYPE html>
153
+ <html>
154
+ <head>
155
+ <title>404 - Page Not Found</title>
156
+ </head>
157
+ <body>
158
+ <h1>404 - Page Not Found</h1>
159
+ <p>The page you requested does not exist.</p>
160
+ </body>
161
+ </html>
162
+ `);
163
+ }
164
+
165
+ // Import the component
166
+ const { default: PageComponent } = await import(componentPath);
167
+
168
+ // Generate page content for meta tags
169
+ const pageContent = `
170
+ Frontend Hamroun SSR Page
171
+ This is a server-rendered page using the Frontend Hamroun framework.
172
+ Path: ${req.path}
173
+ Timestamp: ${new Date().toISOString()}
174
+ `;
175
+
176
+ // Generate meta tags
177
+ const metaTags = await generateMetaTags(pageContent);
178
+
179
+ // Render the component to string
180
+ const content = renderToString(PageComponent());
181
+
182
+ // Send the HTML response
183
+ res.send(`
184
+ <!DOCTYPE html>
185
+ <html lang="en">
186
+ <head>
187
+ <meta charset="UTF-8">
188
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
189
+
190
+ <!-- Generated Meta Tags -->
191
+ <title>${metaTags.title}</title>
192
+ <meta name="description" content="${metaTags.description}">
193
+ <meta name="keywords" content="${metaTags.keywords}">
194
+
195
+ <!-- Open Graph Meta Tags -->
196
+ <meta property="og:title" content="${metaTags.title}">
197
+ <meta property="og:description" content="${metaTags.description}">
198
+ <meta property="og:type" content="website">
199
+ <meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}">
200
+
201
+ <!-- Import Tailwind-like styles for quick styling -->
202
+ <link href="https://cdn.jsdelivr.net/npm/daisyui@3.7.4/dist/full.css" rel="stylesheet" type="text/css" />
203
+ <script src="https://cdn.tailwindcss.com"></script>
204
+
205
+ <!-- Client-side script for hydration -->
206
+ <script type="module" src="/assets/client.js"></script>
207
+ </head>
208
+ <body>
209
+ <div id="app">${content}</div>
210
+
211
+ <!-- Add initial state for hydration -->
212
+ <script>
213
+ window.__INITIAL_STATE__ = ${JSON.stringify({
214
+ path: req.path,
215
+ timestamp: new Date().toISOString(),
216
+ metaTags
217
+ })};
218
+ </script>
219
+ </body>
220
+ </html>
221
+ `);
222
+ } catch (error) {
223
+ console.error('Error rendering page:', error);
224
+ res.status(500).send(`
225
+ <!DOCTYPE html>
226
+ <html>
227
+ <head>
228
+ <title>500 - Server Error</title>
229
+ </head>
230
+ <body>
231
+ <h1>500 - Server Error</h1>
232
+ <p>There was an error processing your request.</p>
233
+ ${process.env.NODE_ENV === 'development' ? `<pre>${error.stack}</pre>` : ''}
234
+ </body>
235
+ </html>
236
+ `);
237
+ }
238
+ });
239
+
240
+ // Add error handler middleware
241
+ app.use(errorHandler);
242
+
243
+ // Add not found handler for API routes that weren't caught
244
+ app.use(notFoundHandler);
245
+
246
+ // Graceful shutdown function to close database connections
247
+ function gracefulShutdown() {
248
+ console.log('Shutting down server...');
249
+
250
+ // Close database connection if it exists
251
+ if (db) {
252
+ db.disconnect()
253
+ .then(() => console.log('Database disconnected'))
254
+ .catch(err => console.error('Error disconnecting from database:', err))
255
+ .finally(() => process.exit(0));
256
+ } else {
257
+ process.exit(0);
258
+ }
259
+ }
260
+
261
+ // Start the server
262
+ const server = app.listen(port, () => {
263
+ console.log(`Server running at http://localhost:${port}`);
264
+ console.log(`Available API endpoints:`);
265
+ console.log(` - GET /api/page-data`);
266
+ console.log(` - POST /api/login`);
267
+ if (process.env.JWT_SECRET) {
268
+ console.log(` - GET /api/protected (requires authentication)`);
269
+ console.log(` - GET /api/admin (requires admin role)`);
270
+ }
271
+ });
272
+
273
+ // Handle graceful shutdown
274
+ process.on('SIGINT', gracefulShutdown);
275
+ process.on('SIGTERM', gracefulShutdown);
@@ -0,0 +1,59 @@
1
+ import { useState, useEffect, useMemo, useRef, createContext } from 'frontend-hamroun';
2
+
3
+ // Create a theme context
4
+ const ThemeContext = createContext('light');
5
+
6
+ export function App() {
7
+ // Initialize with a default state that works on both server and client
8
+ const [count, setCount] = useState(0);
9
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
10
+ const renderCount = useRef(0);
11
+
12
+ // Client-side only effect
13
+ useEffect(() => {
14
+ if (typeof window !== 'undefined') {
15
+ renderCount.current += 1;
16
+ console.log('Component rendered', renderCount.current, 'times');
17
+ }
18
+ return () => console.log('Component unmounting');
19
+ }, [count]);
20
+
21
+ // Memoized value
22
+ const doubled = useMemo(() => count * 2, [count]);
23
+
24
+ return (
25
+ <ThemeContext.Provider value={theme}>
26
+ <div style={{
27
+ padding: '20px',
28
+ backgroundColor: theme === 'dark' ? '#333' : '#fff',
29
+ color: theme === 'dark' ? '#fff' : '#333'
30
+ }}>
31
+ <h1>Server-Side Rendered App</h1>
32
+ <div>
33
+ <button
34
+ onClick={() => setCount(count - 1)}
35
+ data-action="decrement"
36
+ >-</button>
37
+ <span style={{ margin: '0 10px' }}>{count}</span>
38
+ <button
39
+ onClick={() => setCount(count + 1)}
40
+ data-action="increment"
41
+ >+</button>
42
+ </div>
43
+ <p>Doubled value: {doubled}</p>
44
+ {typeof window !== 'undefined' && (
45
+ <p>Render count: {renderCount.current}</p>
46
+ )}
47
+ <button
48
+ onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
49
+ style={{ marginTop: '10px' }}
50
+ >
51
+ Toggle Theme ({theme})
52
+ </button>
53
+ <script dangerouslySetInnerHTML={{
54
+ __html: `window.__INITIAL_STATE__ = ${JSON.stringify({ count: 0, theme: 'light' })};`
55
+ }} />
56
+ </div>
57
+ </ThemeContext.Provider>
58
+ );
59
+ }
@@ -0,0 +1,61 @@
1
+ import { hydrate, jsx } from 'frontend-hamroun';
2
+
3
+ // Dynamically import the appropriate page component
4
+ async function hydratePage() {
5
+ try {
6
+ // Get initial state from server
7
+ const initialState = window.__INITIAL_STATE__ || {};
8
+
9
+ // Get current path
10
+ const path = initialState.route || window.location.pathname;
11
+ const normalizedPath = path === '/' ? '/index' : path;
12
+
13
+ // Create path to module
14
+ const modulePath = `.${normalizedPath.replace(/\/$/, '')}.js`;
15
+
16
+ try {
17
+ // Dynamically import the component
18
+ const module = await import(`./pages${normalizedPath}.js`).catch(() =>
19
+ import(`./pages${normalizedPath}/index.js`));
20
+
21
+ const PageComponent = module.default;
22
+
23
+ // Find the root element
24
+ const rootElement = document.getElementById('root');
25
+
26
+ if (rootElement && PageComponent) {
27
+ // Hydrate the application with the same params from the server
28
+ hydrate(jsx(PageComponent, { params: initialState.params || {} }), rootElement);
29
+ console.log('Hydration complete');
30
+ } else {
31
+ console.error('Could not find root element or page component');
32
+ }
33
+ } catch (importError) {
34
+ console.error('Error importing page component:', importError);
35
+
36
+ // Fallback to App component if available
37
+ try {
38
+ const { App } = await import('./App.js');
39
+ const rootElement = document.getElementById('root');
40
+
41
+ if (rootElement && App) {
42
+ hydrate(jsx(App, {}), rootElement);
43
+ console.log('Fallback hydration complete');
44
+ }
45
+ } catch (fallbackError) {
46
+ console.error('Fallback hydration failed:', fallbackError);
47
+ }
48
+ }
49
+ } catch (error) {
50
+ console.error('Hydration error:', error);
51
+ }
52
+ }
53
+
54
+ // Add global variable for JSX
55
+ window.jsx = jsx;
56
+
57
+ // Hydrate when DOM is ready
58
+ document.addEventListener('DOMContentLoaded', hydratePage);
59
+
60
+ // Handle client-side navigation (if implemented)
61
+ window.addEventListener('popstate', hydratePage);
@@ -0,0 +1,18 @@
1
+ import { hydrate, createElement } from 'frontend-hamroun';
2
+
3
+ // For simplicity in this example, we just hydrate the root component
4
+ // In a more complex app, you might use a router
5
+ import HomePage from './pages/index';
6
+
7
+ // When the DOM is ready, hydrate the server-rendered HTML
8
+ document.addEventListener('DOMContentLoaded', () => {
9
+ const rootElement = document.getElementById('app');
10
+
11
+ if (rootElement) {
12
+ // Hydrate the app with the same component that was rendered on the server
13
+ hydrate(<HomePage />, rootElement);
14
+ console.log('Hydration complete');
15
+ } else {
16
+ console.error('Could not find root element with id "app"');
17
+ }
18
+ });
@@ -0,0 +1,51 @@
1
+ import { useState, useEffect } from 'frontend-hamroun';
2
+
3
+ // This component will be rendered on both server and client
4
+ export default function HomePage() {
5
+ const [count, setCount] = useState(0);
6
+ const [serverTime, setServerTime] = useState('');
7
+
8
+ // This effect only runs on the client after hydration
9
+ useEffect(() => {
10
+ // Set server render time (only when hydrating)
11
+ if (!serverTime) {
12
+ setServerTime('Client-side hydration complete');
13
+ }
14
+
15
+ // Simple cleanup function
16
+ return () => {
17
+ console.log('Component unmounting');
18
+ };
19
+ }, []);
20
+
21
+ return (
22
+ <div id="app">
23
+ <div className="hero min-h-screen bg-base-200">
24
+ <div className="hero-content text-center">
25
+ <div className="max-w-md">
26
+ <h1 className="text-5xl font-bold">Frontend Hamroun SSR</h1>
27
+ <p className="py-6">
28
+ This page was rendered on the server and hydrated on the client.
29
+ </p>
30
+
31
+ {/* Interactive counter demonstrates client-side hydration */}
32
+ <div className="my-4 p-4 bg-base-300 rounded-lg">
33
+ <p>Counter: {count}</p>
34
+ <button
35
+ className="btn btn-primary mt-2"
36
+ onClick={() => setCount(count + 1)}
37
+ >
38
+ Increment
39
+ </button>
40
+ </div>
41
+
42
+ {/* Shows server render time or hydration status */}
43
+ <div className="text-sm opacity-70 mt-4">
44
+ {serverTime ? serverTime : `Server rendered at: ${new Date().toISOString()}`}
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,218 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { renderToString, jsx } from 'frontend-hamroun';
5
+ import { App } from './App.js';
6
+ import fs from 'fs';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const app = express();
10
+ const port = 3000;
11
+
12
+ // Find the client entry file
13
+ const getClientEntry = () => {
14
+ const assetsDir = path.join(__dirname, './');
15
+ const files = fs.readdirSync(assetsDir);
16
+ return files.find(file => file.startsWith("client") && file.endsWith('.js'));
17
+ };
18
+
19
+ // Serve static files from dist/assets
20
+ app.use('/assets', express.static(path.join(__dirname, './')));
21
+
22
+ // Serve static files from dist
23
+ app.use(express.static(path.join(__dirname, 'dist')));
24
+
25
+ // Auto-routing middleware - scans the pages directory for components
26
+ const getPagesDirectory = () => {
27
+ return path.join(__dirname, 'pages');
28
+ };
29
+
30
+ // Helper to check if a file exists
31
+ const fileExists = async (filePath) => {
32
+ try {
33
+ await fs.promises.access(filePath);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ };
39
+
40
+ // Map URL path to component path
41
+ const getComponentPath = async (urlPath) => {
42
+ const pagesDir = getPagesDirectory();
43
+
44
+ // Handle root path
45
+ if (urlPath === '/') {
46
+ const indexPath = path.join(pagesDir, 'index.js');
47
+ if (await fileExists(indexPath)) {
48
+ return {
49
+ componentPath: indexPath,
50
+ params: {}
51
+ };
52
+ }
53
+ }
54
+
55
+ // Try direct match (e.g., /about -> /pages/about.js)
56
+ const directPath = path.join(pagesDir, `${urlPath.slice(1)}.js`);
57
+ if (await fileExists(directPath)) {
58
+ return {
59
+ componentPath: directPath,
60
+ params: {}
61
+ };
62
+ }
63
+
64
+ // Try directory index (e.g., /about -> /pages/about/index.js)
65
+ const dirIndexPath = path.join(pagesDir, urlPath.slice(1), 'index.js');
66
+ if (await fileExists(dirIndexPath)) {
67
+ return {
68
+ componentPath: dirIndexPath,
69
+ params: {}
70
+ };
71
+ }
72
+
73
+ // Look for dynamic routes (with [param] in filename)
74
+ const segments = urlPath.split('/').filter(Boolean);
75
+ const dynamicRoutes = [];
76
+
77
+ // Recursively scan pages directory for all files
78
+ const scanDir = (dir, basePath = '') => {
79
+ const items = fs.readdirSync(dir, { withFileTypes: true });
80
+
81
+ for (const item of items) {
82
+ const itemPath = path.join(dir, item.name);
83
+ const routePath = path.join(basePath, item.name);
84
+
85
+ if (item.isDirectory()) {
86
+ scanDir(itemPath, routePath);
87
+ } else if (item.name.endsWith('.js') && item.name.includes('[')) {
88
+ // This is a dynamic route file
89
+ const urlPattern = routePath
90
+ .replace(/\.js$/, '')
91
+ .replace(/\[([^\]]+)\]/g, ':$1');
92
+
93
+ dynamicRoutes.push({
94
+ pattern: urlPattern,
95
+ componentPath: itemPath
96
+ });
97
+ }
98
+ }
99
+ };
100
+
101
+ scanDir(pagesDir);
102
+
103
+ // Check if any dynamic routes match
104
+ for (const route of dynamicRoutes) {
105
+ const routeSegments = route.pattern.split('/').filter(Boolean);
106
+ if (routeSegments.length !== segments.length) continue;
107
+
108
+ const params = {};
109
+ let matches = true;
110
+
111
+ for (let i = 0; i < segments.length; i++) {
112
+ const routeSeg = routeSegments[i];
113
+ const urlSeg = segments[i];
114
+
115
+ if (routeSeg.startsWith(':')) {
116
+ // This is a parameter
117
+ const paramName = routeSeg.slice(1);
118
+ params[paramName] = urlSeg;
119
+ } else if (routeSeg !== urlSeg) {
120
+ matches = false;
121
+ break;
122
+ }
123
+ }
124
+
125
+ if (matches) {
126
+ return {
127
+ componentPath: route.componentPath,
128
+ params
129
+ };
130
+ }
131
+ }
132
+
133
+ // No match found
134
+ return null;
135
+ };
136
+
137
+ // Handle all routes with auto-routing
138
+ app.get('*', async (req, res) => {
139
+ try {
140
+ const clientEntry = getClientEntry();
141
+ const routeResult = await getComponentPath(req.path);
142
+
143
+ if (!routeResult) {
144
+ return res.status(404).send(`
145
+ <!DOCTYPE html>
146
+ <html>
147
+ <head>
148
+ <title>404 - Page Not Found</title>
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
150
+ </head>
151
+ <body>
152
+ <div id="root">
153
+ <h1>404 - Page Not Found</h1>
154
+ <p>The page you requested could not be found.</p>
155
+ </div>
156
+ <script type="module" src="/assets/${clientEntry}"></script>
157
+ </body>
158
+ </html>
159
+ `);
160
+ }
161
+
162
+ // Import the component dynamically
163
+ const { default: PageComponent } = await import(routeResult.componentPath);
164
+
165
+ if (!PageComponent) {
166
+ throw new Error(`Invalid component in ${routeResult.componentPath}`);
167
+ }
168
+
169
+ // Render the component with params
170
+ const html = await renderToString(jsx(PageComponent, { params: routeResult.params }));
171
+
172
+ // Create route data for client hydration
173
+ const initialState = {
174
+ route: req.path,
175
+ params: routeResult.params,
176
+ timestamp: new Date().toISOString()
177
+ };
178
+
179
+ res.send(`
180
+ <!DOCTYPE html>
181
+ <html>
182
+ <head>
183
+ <title>Frontend Hamroun SSR</title>
184
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
+ <meta charset="UTF-8">
186
+ </head>
187
+ <body>
188
+ <div id="root">${html}</div>
189
+ <script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}</script>
190
+ <script type="module" src="/assets/${clientEntry}"></script>
191
+ </body>
192
+ </html>
193
+ `);
194
+ } catch (error) {
195
+ console.error('Rendering error:', error);
196
+ res.status(500).send(`
197
+ <!DOCTYPE html>
198
+ <html>
199
+ <head>
200
+ <title>500 - Server Error</title>
201
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
202
+ </head>
203
+ <body>
204
+ <div id="root">
205
+ <h1>500 - Server Error</h1>
206
+ <p>Something went wrong on the server.</p>
207
+ <pre>${process.env.NODE_ENV === 'production' ? '' : error.stack}</pre>
208
+ </div>
209
+ <script type="module" src="/assets/${clientEntry}"></script>
210
+ </body>
211
+ </html>
212
+ `);
213
+ }
214
+ });
215
+
216
+ app.listen(port, () => {
217
+ console.log(`Server running at http://localhost:${port}`);
218
+ });