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.
- package/dist/batch/package.json +1 -1
- package/dist/client-router/package.json +1 -1
- package/dist/component/package.json +1 -1
- package/dist/context/package.json +1 -1
- package/dist/event-bus/package.json +1 -1
- package/dist/forms/package.json +1 -1
- package/dist/hooks/package.json +1 -1
- package/dist/index.mjs +1 -0
- package/dist/jsx-runtime/package.json +1 -1
- package/dist/lifecycle-events/package.json +1 -1
- package/dist/package.json +1 -1
- package/dist/render-component/package.json +1 -1
- package/dist/renderer/package.json +1 -1
- package/dist/router/package.json +1 -1
- package/dist/server/package.json +1 -1
- package/dist/server/src/index.js +24 -23
- package/dist/server/src/index.js.map +1 -1
- package/dist/server/src/renderComponent.d.ts +8 -9
- package/dist/server/src/renderComponent.js +10 -5
- package/dist/server/src/renderComponent.js.map +1 -1
- package/dist/server/src/server/index.d.ts +23 -34
- package/dist/server/src/server/index.js +170 -50
- package/dist/server/src/server/index.js.map +1 -1
- package/dist/server/src/server/templates.d.ts +2 -0
- package/dist/server/src/server/templates.js +9 -5
- package/dist/server/src/server/templates.js.map +1 -1
- package/dist/server/src/server/utils.d.ts +1 -1
- package/dist/server/src/server/utils.js +1 -1
- package/dist/server/src/server/utils.js.map +1 -1
- package/dist/server/tsconfig.server.tsbuildinfo +1 -1
- package/dist/server-renderer/package.json +1 -1
- package/dist/store/package.json +1 -1
- package/dist/types/package.json +1 -1
- package/dist/utils/package.json +1 -1
- package/dist/vdom/package.json +1 -1
- package/dist/wasm/package.json +1 -1
- package/package.json +1 -1
- package/templates/complete-app/client.js +58 -0
- package/templates/complete-app/package-lock.json +2536 -0
- package/templates/complete-app/package.json +8 -31
- package/templates/complete-app/pages/about.js +119 -0
- package/templates/complete-app/pages/index.js +157 -0
- package/templates/complete-app/pages/wasm-demo.js +290 -0
- package/templates/complete-app/public/client.js +80 -0
- package/templates/complete-app/public/index.html +47 -0
- package/templates/complete-app/public/styles.css +446 -212
- package/templates/complete-app/readme.md +188 -0
- package/templates/complete-app/server.js +417 -0
- package/templates/complete-app/server.ts +275 -0
- package/templates/complete-app/src/App.tsx +59 -0
- package/templates/complete-app/src/client.ts +61 -0
- package/templates/complete-app/src/client.tsx +18 -0
- package/templates/complete-app/src/pages/index.tsx +51 -0
- package/templates/complete-app/src/server.ts +218 -0
- package/templates/complete-app/tsconfig.json +22 -0
- package/templates/complete-app/tsconfig.server.json +19 -0
- package/templates/complete-app/vite.config.js +57 -0
- package/templates/complete-app/vite.config.ts +30 -0
- package/templates/go/example.go +154 -99
- package/templates/complete-app/build.js +0 -284
- package/templates/complete-app/src/api/index.js +0 -31
- package/templates/complete-app/src/client.js +0 -93
- package/templates/complete-app/src/components/App.js +0 -66
- package/templates/complete-app/src/components/Footer.js +0 -19
- package/templates/complete-app/src/components/Header.js +0 -38
- package/templates/complete-app/src/pages/About.js +0 -59
- package/templates/complete-app/src/pages/Home.js +0 -54
- package/templates/complete-app/src/pages/WasmDemo.js +0 -136
- package/templates/complete-app/src/server.js +0 -186
- package/templates/complete-app/src/wasm/build.bat +0 -16
- package/templates/complete-app/src/wasm/build.sh +0 -16
- package/templates/complete-app/src/wasm/example.go +0 -101
@@ -0,0 +1,188 @@
|
|
1
|
+
# Frontend Hamroun SSR Template
|
2
|
+
|
3
|
+
This is a comprehensive server-side rendering (SSR) example using Frontend Hamroun.
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
1. Install dependencies:
|
8
|
+
```
|
9
|
+
npm install
|
10
|
+
```
|
11
|
+
|
12
|
+
2. Start the server:
|
13
|
+
```
|
14
|
+
npm start
|
15
|
+
```
|
16
|
+
|
17
|
+
3. Open your browser at http://localhost:3000
|
18
|
+
|
19
|
+
## Core Features
|
20
|
+
|
21
|
+
This template demonstrates:
|
22
|
+
|
23
|
+
### Server-Side Rendering
|
24
|
+
- Pre-rendering of components on the server
|
25
|
+
- Hydration of server-rendered content on the client
|
26
|
+
- Data fetching during server rendering
|
27
|
+
- AI-powered meta tag generation
|
28
|
+
|
29
|
+
### Automatic File-Based Routing
|
30
|
+
- Pages are automatically rendered based on their file path in the `pages` directory
|
31
|
+
- For example:
|
32
|
+
- `/pages/index.js` → `/` route
|
33
|
+
- `/pages/about.js` → `/about` route
|
34
|
+
- `/pages/blog/index.js` → `/blog` route
|
35
|
+
- `/pages/users/[id].js` → `/users/:id` dynamic route
|
36
|
+
|
37
|
+
### API Integration
|
38
|
+
- RESTful API endpoints
|
39
|
+
- Dynamic API routing
|
40
|
+
- API middleware for validation and security
|
41
|
+
- File-based API structure
|
42
|
+
|
43
|
+
### Database Integration
|
44
|
+
- MongoDB, MySQL, and PostgreSQL support
|
45
|
+
- ORM-like query interface
|
46
|
+
- Connection pooling
|
47
|
+
- Transaction support
|
48
|
+
|
49
|
+
### Authentication & Authorization
|
50
|
+
- JWT-based authentication
|
51
|
+
- Role-based access control
|
52
|
+
- Password hashing and validation
|
53
|
+
- Token refresh mechanism
|
54
|
+
|
55
|
+
### Performance Optimization
|
56
|
+
- Caching strategies
|
57
|
+
- Response compression
|
58
|
+
- Static asset optimization
|
59
|
+
- Efficient metadata handling
|
60
|
+
|
61
|
+
## Implementation Examples
|
62
|
+
|
63
|
+
### Creating Components
|
64
|
+
|
65
|
+
Use the `jsx` or `createElement` function from 'frontend-hamroun':
|
66
|
+
|
67
|
+
```jsx
|
68
|
+
import { jsx } from 'frontend-hamroun';
|
69
|
+
|
70
|
+
export default function MyComponent(props) {
|
71
|
+
return jsx('div', { className: "container" }, [
|
72
|
+
jsx('h1', {}, "Hello World"),
|
73
|
+
jsx('p', {}, `Props value: ${props.value}`)
|
74
|
+
]);
|
75
|
+
}
|
76
|
+
```
|
77
|
+
|
78
|
+
### Creating API Routes
|
79
|
+
|
80
|
+
Create files in the `api` directory following this pattern:
|
81
|
+
|
82
|
+
```typescript
|
83
|
+
// api/users/index.ts
|
84
|
+
import { Request, Response } from 'express';
|
85
|
+
|
86
|
+
export const get = (req: Request, res: Response) => {
|
87
|
+
res.json({ users: [...] });
|
88
|
+
};
|
89
|
+
|
90
|
+
export const post = (req: Request, res: Response) => {
|
91
|
+
// Create user
|
92
|
+
res.status(201).json({ success: true });
|
93
|
+
};
|
94
|
+
```
|
95
|
+
|
96
|
+
### Database Usage
|
97
|
+
|
98
|
+
```typescript
|
99
|
+
import { Server } from 'frontend-hamroun/server';
|
100
|
+
|
101
|
+
const server = new Server({
|
102
|
+
db: {
|
103
|
+
url: process.env.DATABASE_URL,
|
104
|
+
type: 'mongodb' // or 'mysql', 'postgres'
|
105
|
+
}
|
106
|
+
});
|
107
|
+
|
108
|
+
// Get typed database instance
|
109
|
+
const db = server.getDatabase();
|
110
|
+
const users = await db.query('SELECT * FROM users'); // For SQL
|
111
|
+
const docs = await db.getMongoDb().collection('users').find().toArray(); // For MongoDB
|
112
|
+
```
|
113
|
+
|
114
|
+
### Authentication
|
115
|
+
|
116
|
+
```typescript
|
117
|
+
import { AuthService } from 'frontend-hamroun/server';
|
118
|
+
|
119
|
+
const auth = new AuthService({
|
120
|
+
secret: process.env.JWT_SECRET,
|
121
|
+
expiresIn: '24h'
|
122
|
+
});
|
123
|
+
|
124
|
+
// Protect routes
|
125
|
+
app.get('/api/protected', auth.requireAuth(), (req, res) => {
|
126
|
+
res.json({ message: "Authenticated!" });
|
127
|
+
});
|
128
|
+
|
129
|
+
// Create user & login
|
130
|
+
const hashedPassword = await auth.hashPassword(password);
|
131
|
+
const token = auth.generateToken(user);
|
132
|
+
```
|
133
|
+
|
134
|
+
### Advanced Middleware
|
135
|
+
|
136
|
+
```typescript
|
137
|
+
import { rateLimit, requestLogger, errorHandler } from 'frontend-hamroun/server';
|
138
|
+
|
139
|
+
app.use(requestLogger);
|
140
|
+
app.use('/api', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
|
141
|
+
app.use(errorHandler);
|
142
|
+
```
|
143
|
+
|
144
|
+
## File-Based Routing Examples
|
145
|
+
|
146
|
+
### Static Routes
|
147
|
+
Create files in the `pages` directory:
|
148
|
+
|
149
|
+
```jsx
|
150
|
+
// pages/index.js - Maps to "/"
|
151
|
+
export default function HomePage() {
|
152
|
+
return <h1>Home Page</h1>;
|
153
|
+
}
|
154
|
+
|
155
|
+
// pages/about.js - Maps to "/about"
|
156
|
+
export default function AboutPage() {
|
157
|
+
return <h1>About Us</h1>;
|
158
|
+
}
|
159
|
+
|
160
|
+
// pages/contact/index.js - Maps to "/contact"
|
161
|
+
export default function ContactPage() {
|
162
|
+
return <h1>Contact Us</h1>;
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
### Dynamic Routes
|
167
|
+
Use brackets in filenames to define dynamic parameters:
|
168
|
+
|
169
|
+
```jsx
|
170
|
+
// pages/users/[id].js - Maps to "/users/:id"
|
171
|
+
export default function UserPage({ params }) {
|
172
|
+
return <h1>User Profile: {params.id}</h1>;
|
173
|
+
}
|
174
|
+
|
175
|
+
// pages/blog/[category]/[slug].js - Maps to "/blog/:category/:slug"
|
176
|
+
export default function BlogPost({ params }) {
|
177
|
+
return (
|
178
|
+
<div>
|
179
|
+
<h1>Blog Post: {params.slug}</h1>
|
180
|
+
<p>Category: {params.category}</p>
|
181
|
+
</div>
|
182
|
+
);
|
183
|
+
}
|
184
|
+
```
|
185
|
+
|
186
|
+
## Next Steps
|
187
|
+
|
188
|
+
Explore the full API documentation for more advanced features and customization options.
|
@@ -0,0 +1,417 @@
|
|
1
|
+
import express from 'express';
|
2
|
+
import path from 'path';
|
3
|
+
import { fileURLToPath } from 'url';
|
4
|
+
import fs from 'fs';
|
5
|
+
import {
|
6
|
+
renderToString,
|
7
|
+
jsx,
|
8
|
+
Server,
|
9
|
+
createServer,
|
10
|
+
rateLimit,
|
11
|
+
requestLogger,
|
12
|
+
errorHandler,
|
13
|
+
notFoundHandler,
|
14
|
+
loadGoWasmFromFile
|
15
|
+
} from 'frontend-hamroun';
|
16
|
+
import dotenv from 'dotenv';
|
17
|
+
import compression from 'compression';
|
18
|
+
import cors from 'cors';
|
19
|
+
|
20
|
+
// Load environment variables
|
21
|
+
dotenv.config();
|
22
|
+
|
23
|
+
// Get __dirname equivalent in ESM
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
25
|
+
const __dirname = path.dirname(__filename);
|
26
|
+
|
27
|
+
// Create Express app
|
28
|
+
const app = express();
|
29
|
+
const PORT = process.env.PORT || 3000;
|
30
|
+
|
31
|
+
// Middleware
|
32
|
+
app.use(compression()); // Compress responses
|
33
|
+
app.use(express.json()); // Parse JSON requests
|
34
|
+
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded requests
|
35
|
+
app.use(cors()); // Enable CORS
|
36
|
+
app.use(requestLogger); // Log all requests
|
37
|
+
|
38
|
+
// Add rate limiting to prevent abuse
|
39
|
+
app.use(rateLimit({
|
40
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
41
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
42
|
+
}));
|
43
|
+
|
44
|
+
// Serve static files from the public directory
|
45
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
46
|
+
|
47
|
+
// Add correct MIME type for client.js module
|
48
|
+
app.get('/client.js', (req, res) => {
|
49
|
+
res.type('application/javascript').sendFile(path.join(__dirname, 'public', 'client.js'));
|
50
|
+
});
|
51
|
+
|
52
|
+
// Serve WebAssembly files with correct MIME type
|
53
|
+
app.get('*.wasm', (req, res, next) => {
|
54
|
+
res.type('application/wasm');
|
55
|
+
next();
|
56
|
+
});
|
57
|
+
|
58
|
+
// Add API routes example with authentication
|
59
|
+
app.get('/api/hello', (req, res) => {
|
60
|
+
res.json({
|
61
|
+
message: 'Hello from the API!',
|
62
|
+
serverTime: new Date().toISOString()
|
63
|
+
});
|
64
|
+
});
|
65
|
+
|
66
|
+
// Load WebAssembly modules if available
|
67
|
+
async function loadWasmModules() {
|
68
|
+
const wasmDir = path.join(__dirname, 'wasm');
|
69
|
+
|
70
|
+
if (fs.existsSync(wasmDir)) {
|
71
|
+
try {
|
72
|
+
const wasmFiles = fs.readdirSync(wasmDir).filter(file => file.endsWith('.wasm'));
|
73
|
+
|
74
|
+
for (const wasmFile of wasmFiles) {
|
75
|
+
const wasmPath = path.join(wasmDir, wasmFile);
|
76
|
+
const wasmModule = await loadGoWasmFromFile(wasmPath, { debug: true });
|
77
|
+
|
78
|
+
console.log(`Loaded WASM module: ${wasmFile}`);
|
79
|
+
console.log(`Available functions:`, Object.keys(wasmModule.functions));
|
80
|
+
|
81
|
+
// Store modules globally for API routes to use
|
82
|
+
global.wasmModules = global.wasmModules || {};
|
83
|
+
global.wasmModules[wasmFile.replace('.wasm', '')] = wasmModule;
|
84
|
+
}
|
85
|
+
} catch (error) {
|
86
|
+
console.error('Failed to load WASM modules:', error);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
// Find the pages directory
|
92
|
+
const getPagesDirectory = () => {
|
93
|
+
return path.join(__dirname, 'pages');
|
94
|
+
};
|
95
|
+
|
96
|
+
// Helper to check if a file exists
|
97
|
+
const fileExists = async (filePath) => {
|
98
|
+
try {
|
99
|
+
await fs.promises.access(filePath);
|
100
|
+
return true;
|
101
|
+
} catch {
|
102
|
+
return false;
|
103
|
+
}
|
104
|
+
};
|
105
|
+
|
106
|
+
// Generate meta tags for SEO
|
107
|
+
async function generateMetaTags(content, title = '') {
|
108
|
+
// Extract title from content or use provided title
|
109
|
+
const pageTitle = title || 'Frontend Hamroun App';
|
110
|
+
|
111
|
+
// Generate description from content
|
112
|
+
const plainText = content.replace(/<[^>]*>/g, ' ').trim();
|
113
|
+
const description = plainText.substring(0, 160) + (plainText.length > 160 ? '...' : '');
|
114
|
+
|
115
|
+
// Extract keywords
|
116
|
+
const keywords = plainText
|
117
|
+
.toLowerCase()
|
118
|
+
.replace(/[^\w\s]/g, '')
|
119
|
+
.split(/\s+/)
|
120
|
+
.filter(w => w.length > 3)
|
121
|
+
.slice(0, 5)
|
122
|
+
.join(', ');
|
123
|
+
|
124
|
+
return {
|
125
|
+
title: pageTitle,
|
126
|
+
description,
|
127
|
+
keywords
|
128
|
+
};
|
129
|
+
}
|
130
|
+
|
131
|
+
// Map URL path to component path
|
132
|
+
const getComponentPath = async (urlPath) => {
|
133
|
+
const pagesDir = getPagesDirectory();
|
134
|
+
|
135
|
+
// Handle root path
|
136
|
+
if (urlPath === '/') {
|
137
|
+
const indexPath = path.join(pagesDir, 'index.js');
|
138
|
+
if (await fileExists(indexPath)) {
|
139
|
+
return {
|
140
|
+
componentPath: indexPath,
|
141
|
+
params: {}
|
142
|
+
};
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
// Try direct match (e.g., /about -> /pages/about.js)
|
147
|
+
// Only look for .js files since Node.js ESM doesn't support .jsx directly
|
148
|
+
const possibleExtensions = ['.js']; // Remove .jsx, .ts, .tsx
|
149
|
+
|
150
|
+
for (const ext of possibleExtensions) {
|
151
|
+
const directPath = path.join(pagesDir, `${urlPath.slice(1)}${ext}`);
|
152
|
+
if (await fileExists(directPath)) {
|
153
|
+
return {
|
154
|
+
componentPath: directPath,
|
155
|
+
params: {}
|
156
|
+
};
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
// Try directory index (e.g., /about -> /pages/about/index.js)
|
161
|
+
for (const ext of possibleExtensions) {
|
162
|
+
const dirIndexPath = path.join(pagesDir, urlPath.slice(1), `index${ext}`);
|
163
|
+
if (await fileExists(dirIndexPath)) {
|
164
|
+
return {
|
165
|
+
componentPath: dirIndexPath,
|
166
|
+
params: {}
|
167
|
+
};
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
// Look for dynamic routes (with [param] in filename)
|
172
|
+
const segments = urlPath.split('/').filter(Boolean);
|
173
|
+
const dynamicRoutes = [];
|
174
|
+
|
175
|
+
// Recursively scan pages directory for all files
|
176
|
+
const scanDir = (dir, basePath = '') => {
|
177
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
178
|
+
|
179
|
+
for (const item of items) {
|
180
|
+
const itemPath = path.join(dir, item.name);
|
181
|
+
const routePath = path.join(basePath, item.name);
|
182
|
+
|
183
|
+
if (item.isDirectory()) {
|
184
|
+
scanDir(itemPath, routePath);
|
185
|
+
} else if (item.name.endsWith('.js')) {
|
186
|
+
if (item.name.includes('[')) {
|
187
|
+
// This is a dynamic route file
|
188
|
+
const urlPattern = routePath
|
189
|
+
.replace(/\.js$/, '')
|
190
|
+
.replace(/\[([^\]]+)\]/g, ':$1');
|
191
|
+
|
192
|
+
dynamicRoutes.push({
|
193
|
+
pattern: urlPattern,
|
194
|
+
componentPath: itemPath
|
195
|
+
});
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
};
|
200
|
+
|
201
|
+
scanDir(pagesDir);
|
202
|
+
|
203
|
+
// Check if any dynamic routes match
|
204
|
+
for (const route of dynamicRoutes) {
|
205
|
+
const routeSegments = route.pattern.split('/').filter(Boolean);
|
206
|
+
if (routeSegments.length !== segments.length) continue;
|
207
|
+
|
208
|
+
const params = {};
|
209
|
+
let matches = true;
|
210
|
+
|
211
|
+
for (let i = 0; i < segments.length; i++) {
|
212
|
+
const routeSeg = routeSegments[i];
|
213
|
+
const urlSeg = segments[i];
|
214
|
+
|
215
|
+
if (routeSeg.startsWith(':')) {
|
216
|
+
// This is a parameter
|
217
|
+
const paramName = routeSeg.slice(1);
|
218
|
+
params[paramName] = urlSeg;
|
219
|
+
} else if (routeSeg !== urlSeg) {
|
220
|
+
matches = false;
|
221
|
+
break;
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
if (matches) {
|
226
|
+
return {
|
227
|
+
componentPath: route.componentPath,
|
228
|
+
params
|
229
|
+
};
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
// No match found
|
234
|
+
return null;
|
235
|
+
};
|
236
|
+
|
237
|
+
// Handle all routes with SSR
|
238
|
+
app.get('*', async (req, res, next) => {
|
239
|
+
try {
|
240
|
+
// Skip static assets and API routes
|
241
|
+
if (req.path.match(/\.(js|css|ico|png|jpg|jpeg|gif|svg|wasm)$/) || req.path.startsWith('/api/')) {
|
242
|
+
return next();
|
243
|
+
}
|
244
|
+
|
245
|
+
const routeResult = await getComponentPath(req.path);
|
246
|
+
|
247
|
+
if (!routeResult) {
|
248
|
+
return res.status(404).send(`
|
249
|
+
<!DOCTYPE html>
|
250
|
+
<html>
|
251
|
+
<head>
|
252
|
+
<title>404 - Page Not Found</title>
|
253
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
254
|
+
<link href="/styles.css" rel="stylesheet" type="text/css">
|
255
|
+
</head>
|
256
|
+
<body>
|
257
|
+
<div id="app">
|
258
|
+
<div class="container">
|
259
|
+
<h1>404 - Page Not Found</h1>
|
260
|
+
<p>The page you requested could not be found.</p>
|
261
|
+
<a href="/" class="button">Go to Home</a>
|
262
|
+
</div>
|
263
|
+
</div>
|
264
|
+
<script id="__APP_DATA__" type="application/json">${JSON.stringify({
|
265
|
+
path: req.path,
|
266
|
+
error: 'not_found'
|
267
|
+
})}</script>
|
268
|
+
<script type="module" src="/client.js"></script>
|
269
|
+
</body>
|
270
|
+
</html>
|
271
|
+
`);
|
272
|
+
}
|
273
|
+
|
274
|
+
// Fixed component loading to use file:// URL scheme for Windows
|
275
|
+
const componentUrl = new URL(`file://${routeResult.componentPath}`).href;
|
276
|
+
|
277
|
+
try {
|
278
|
+
// Import the component - handle both module.default and direct module exports
|
279
|
+
const moduleImport = await import(componentUrl);
|
280
|
+
const PageComponent = moduleImport.default || moduleImport;
|
281
|
+
|
282
|
+
if (!PageComponent || typeof PageComponent !== 'function') {
|
283
|
+
throw new Error(`Invalid component in ${routeResult.componentPath}: component is not a function`);
|
284
|
+
}
|
285
|
+
|
286
|
+
// Create props with route data
|
287
|
+
const initialProps = {
|
288
|
+
params: routeResult.params,
|
289
|
+
path: req.path,
|
290
|
+
query: req.query,
|
291
|
+
api: {
|
292
|
+
serverTime: new Date().toISOString()
|
293
|
+
}
|
294
|
+
};
|
295
|
+
|
296
|
+
// Use a simpler rendering approach for more reliability
|
297
|
+
let html;
|
298
|
+
try {
|
299
|
+
// Create a simple HTML structure based on the component's output
|
300
|
+
const result = PageComponent(initialProps);
|
301
|
+
|
302
|
+
// Create a fallback HTML if the result is not valid
|
303
|
+
if (!result || typeof result !== 'object' || !result.type) {
|
304
|
+
html = `
|
305
|
+
<div class="error-container">
|
306
|
+
<h1>Frontend Hamroun App</h1>
|
307
|
+
<p>This is a simple fallback page.</p>
|
308
|
+
<p>Path: ${req.path}</p>
|
309
|
+
<p>Server time: ${initialProps.api.serverTime}</p>
|
310
|
+
</div>
|
311
|
+
`;
|
312
|
+
} else {
|
313
|
+
// Use renderToString with error handling
|
314
|
+
html = renderToString(result) || '<div>Rendering failed</div>';
|
315
|
+
}
|
316
|
+
} catch (err) {
|
317
|
+
console.error("Error with renderToString:", err);
|
318
|
+
html = `
|
319
|
+
<div class="error-container">
|
320
|
+
<h1>Rendering Error</h1>
|
321
|
+
<p>There was an error rendering this page: ${err.message}</p>
|
322
|
+
<p>Path: ${req.path}</p>
|
323
|
+
</div>
|
324
|
+
`;
|
325
|
+
}
|
326
|
+
|
327
|
+
// Generate meta tags
|
328
|
+
const metaTags = await generateMetaTags(html,
|
329
|
+
typeof PageComponent.getTitle === 'function' ? PageComponent.getTitle(initialProps) : 'Frontend Hamroun App');
|
330
|
+
|
331
|
+
// Send complete HTML with hydration data
|
332
|
+
res.send(`
|
333
|
+
<!DOCTYPE html>
|
334
|
+
<html lang="en">
|
335
|
+
<head>
|
336
|
+
<meta charset="UTF-8">
|
337
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
338
|
+
|
339
|
+
<!-- Generated Meta Tags -->
|
340
|
+
<title>${metaTags.title}</title>
|
341
|
+
<meta name="description" content="${metaTags.description}">
|
342
|
+
<meta name="keywords" content="${metaTags.keywords}">
|
343
|
+
|
344
|
+
<!-- Open Graph Meta Tags -->
|
345
|
+
<meta property="og:title" content="${metaTags.title}">
|
346
|
+
<meta property="og:description" content="${metaTags.description}">
|
347
|
+
<meta property="og:type" content="website">
|
348
|
+
<meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}">
|
349
|
+
|
350
|
+
<!-- Import styles -->
|
351
|
+
<link href="/styles.css" rel="stylesheet" type="text/css">
|
352
|
+
</head>
|
353
|
+
<body>
|
354
|
+
<div id="app">${html}</div>
|
355
|
+
|
356
|
+
<!-- Add initial state for hydration -->
|
357
|
+
<script id="__APP_DATA__" type="application/json">${JSON.stringify(initialProps)}</script>
|
358
|
+
<script type="module" src="/client.js"></script>
|
359
|
+
</body>
|
360
|
+
</html>
|
361
|
+
`);
|
362
|
+
} catch (importError) {
|
363
|
+
console.error(`Error importing component from ${componentUrl}:`, importError);
|
364
|
+
throw new Error(`Failed to import component: ${importError.message}`);
|
365
|
+
}
|
366
|
+
} catch (error) {
|
367
|
+
console.error('Rendering error:', error);
|
368
|
+
res.status(500).send(`
|
369
|
+
<!DOCTYPE html>
|
370
|
+
<html>
|
371
|
+
<head>
|
372
|
+
<title>500 - Server Error</title>
|
373
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
374
|
+
<link href="/styles.css" rel="stylesheet" type="text/css">
|
375
|
+
</head>
|
376
|
+
<body>
|
377
|
+
<div id="app">
|
378
|
+
<div class="container">
|
379
|
+
<h1>500 - Server Error</h1>
|
380
|
+
<p>Something went wrong on the server.</p>
|
381
|
+
${process.env.NODE_ENV === 'development' ? `<pre>${error.stack}</pre>` : ''}
|
382
|
+
<a href="/" class="button">Go to Home</a>
|
383
|
+
</div>
|
384
|
+
</div>
|
385
|
+
<script id="__APP_DATA__" type="application/json">${JSON.stringify({
|
386
|
+
path: req.path,
|
387
|
+
error: 'server_error'
|
388
|
+
})}</script>
|
389
|
+
<script type="module" src="/client.js"></script>
|
390
|
+
</body>
|
391
|
+
</html>
|
392
|
+
`);
|
393
|
+
}
|
394
|
+
});
|
395
|
+
|
396
|
+
// Add error handler middleware
|
397
|
+
app.use(errorHandler);
|
398
|
+
|
399
|
+
// Add 404 handler for API routes
|
400
|
+
app.use(notFoundHandler);
|
401
|
+
|
402
|
+
// Start the server
|
403
|
+
const startServer = async () => {
|
404
|
+
// Load WebAssembly modules if available
|
405
|
+
await loadWasmModules();
|
406
|
+
|
407
|
+
// Start the server
|
408
|
+
app.listen(PORT, () => {
|
409
|
+
console.log(`Server running at http://localhost:${PORT}`);
|
410
|
+
console.log(`Mode: ${process.env.NODE_ENV || 'development'}`);
|
411
|
+
});
|
412
|
+
};
|
413
|
+
|
414
|
+
startServer().catch(err => {
|
415
|
+
console.error('Failed to start server:', err);
|
416
|
+
process.exit(1);
|
417
|
+
});
|