baraqex 2.0.2 → 2.0.4
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/browser.js +11 -9
- package/dist/browser.js.map +2 -2
- package/dist/core/jsx-runtime.d.ts +1 -1
- package/dist/core/jsx-runtime.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/index.js.map +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +2 -2
- package/package.json +1 -1
- package/templates/fullstack-app/README.md +62 -37
- package/templates/fullstack-app/build-wasm.js +80 -0
- package/templates/fullstack-app/build.mjs +133 -0
- package/templates/fullstack-app/go/main.go +53 -0
- package/templates/fullstack-app/index.html +16 -36
- package/templates/fullstack-app/package.json +41 -35
- package/templates/fullstack-app/postcss.config.js +6 -6
- package/templates/fullstack-app/public/wasm/.gitkeep +1 -0
- package/templates/fullstack-app/server.ts +220 -474
- package/templates/fullstack-app/src/App.tsx +77 -0
- package/templates/fullstack-app/src/main.tsx +31 -155
- package/templates/fullstack-app/src/pages/About.tsx +40 -0
- package/templates/fullstack-app/src/pages/Home.tsx +67 -0
- package/templates/fullstack-app/src/pages/WasmDemo.tsx +81 -0
- package/templates/fullstack-app/src/styles.css +12 -5
- package/templates/fullstack-app/tailwind.config.js +8 -11
- package/templates/fullstack-app/tsconfig.json +18 -18
- package/templates/fullstack-app/api/hello.js +0 -11
- package/templates/fullstack-app/api/hello.ts +0 -18
- package/templates/fullstack-app/api/users/[id].ts +0 -73
- package/templates/fullstack-app/api/users/index.ts +0 -32
- package/templates/fullstack-app/build/main.css +0 -874
- package/templates/fullstack-app/build/main.css.map +0 -7
- package/templates/fullstack-app/build/main.js +0 -967
- package/templates/fullstack-app/build/main.js.map +0 -7
- package/templates/fullstack-app/build.ts +0 -210
- package/templates/fullstack-app/process-tailwind.js +0 -45
- package/templates/fullstack-app/public/_redirects +0 -1
- package/templates/fullstack-app/public/route-handler.js +0 -47
- package/templates/fullstack-app/public/spa-fix.html +0 -17
- package/templates/fullstack-app/public/styles.css +0 -975
- package/templates/fullstack-app/public/tailwind.css +0 -15
- package/templates/fullstack-app/server.js +0 -267
- package/templates/fullstack-app/src/README.md +0 -55
- package/templates/fullstack-app/src/client.js +0 -87
- package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
- package/templates/fullstack-app/src/components/ErrorBoundary.tsx +0 -36
- package/templates/fullstack-app/src/components/Layout.tsx +0 -42
- package/templates/fullstack-app/src/components/StateDemo.tsx +0 -207
- package/templates/fullstack-app/src/components/UserList.tsx +0 -44
- package/templates/fullstack-app/src/config.ts +0 -42
- package/templates/fullstack-app/src/data/api.ts +0 -206
- package/templates/fullstack-app/src/middleware.ts +0 -28
- package/templates/fullstack-app/src/pages/404.tsx +0 -28
- package/templates/fullstack-app/src/pages/[id].tsx +0 -0
- package/templates/fullstack-app/src/pages/_app.tsx +0 -11
- package/templates/fullstack-app/src/pages/_document.tsx +0 -25
- package/templates/fullstack-app/src/pages/_error.tsx +0 -45
- package/templates/fullstack-app/src/pages/about/index.tsx +0 -67
- package/templates/fullstack-app/src/pages/about.tsx +0 -71
- package/templates/fullstack-app/src/pages/api/users/[id].ts +0 -73
- package/templates/fullstack-app/src/pages/api/users/index.ts +0 -43
- package/templates/fullstack-app/src/pages/index.tsx +0 -110
- package/templates/fullstack-app/src/pages/users/[id].tsx +0 -153
- package/templates/fullstack-app/src/pages/users.tsx +0 -60
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +0 -1
- package/templates/fullstack-app/src/router.ts +0 -255
- package/templates/fullstack-app/vite.config.js +0 -87
|
@@ -1,474 +1,220 @@
|
|
|
1
|
-
import express, { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import fs from 'fs/promises';
|
|
5
|
-
import { existsSync
|
|
6
|
-
import dotenv from 'dotenv';
|
|
7
|
-
import { createServer } from 'http';
|
|
8
|
-
import { Server as SocketServer } from 'socket.io';
|
|
9
|
-
import compression from 'compression';
|
|
10
|
-
import cors from 'cors';
|
|
11
|
-
import * as esbuild from 'esbuild';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
name: '
|
|
104
|
-
setup(build
|
|
105
|
-
build.onLoad({ filter: /\.
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
case '.jsx':
|
|
222
|
-
loader = 'jsx';
|
|
223
|
-
break;
|
|
224
|
-
case '.css':
|
|
225
|
-
contentType = 'text/css';
|
|
226
|
-
loader = 'css';
|
|
227
|
-
break;
|
|
228
|
-
case '.json':
|
|
229
|
-
contentType = 'application/json';
|
|
230
|
-
loader = 'json';
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Transform the file with esbuild
|
|
235
|
-
const result = await esbuild.transform(source, {
|
|
236
|
-
loader: loader as esbuild.Loader,
|
|
237
|
-
sourcemap: 'inline',
|
|
238
|
-
jsxFactory: 'jsx',
|
|
239
|
-
jsxFragment: 'Fragment'
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
res.setHeader('Content-Type', contentType);
|
|
243
|
-
res.send(result.code);
|
|
244
|
-
} catch (error) {
|
|
245
|
-
console.error(`Error serving ${req.path}:`, error);
|
|
246
|
-
next(error);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Helper function to check if file exists
|
|
252
|
-
async function fileExists(filepath: PathLike) {
|
|
253
|
-
try {
|
|
254
|
-
await fs.access(filepath);
|
|
255
|
-
return true;
|
|
256
|
-
} catch {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Import server utilities directly without WASM functionality
|
|
262
|
-
import { renderToString } from 'frontend-hamroun';
|
|
263
|
-
|
|
264
|
-
// Simple renderComponent implementation
|
|
265
|
-
async function renderComponent(Component: (arg0: {}) => any, props = {}) {
|
|
266
|
-
try {
|
|
267
|
-
// Create HTML string from component
|
|
268
|
-
const html = renderToString(Component(props));
|
|
269
|
-
return {
|
|
270
|
-
html,
|
|
271
|
-
success: true
|
|
272
|
-
};
|
|
273
|
-
} catch (error) {
|
|
274
|
-
console.error('Error rendering component:', error);
|
|
275
|
-
return {
|
|
276
|
-
html: `<div class="error">Error rendering component</div>`,
|
|
277
|
-
success: false,
|
|
278
|
-
error
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// SSR handler for all routes except /api and static files
|
|
284
|
-
app.get('*', async (req, res, next) => {
|
|
285
|
-
// Skip API routes, static assets, and build/src files
|
|
286
|
-
if (req.path.startsWith('/api/') ||
|
|
287
|
-
req.path.startsWith('/build/') ||
|
|
288
|
-
req.path.startsWith('/src/') ||
|
|
289
|
-
req.path.match(/\.(js|css|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
|
|
290
|
-
return next();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
console.log(`[SSR] Processing request for: ${req.path}`);
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
// Normalize the path
|
|
297
|
-
const pagePath = req.path === '/' ? 'index' : req.path.slice(1);
|
|
298
|
-
|
|
299
|
-
// Create initial state
|
|
300
|
-
const initialState = {
|
|
301
|
-
route: req.path,
|
|
302
|
-
timestamp: new Date().toISOString(),
|
|
303
|
-
serverRendered: true,
|
|
304
|
-
data: {
|
|
305
|
-
users: store.users || [],
|
|
306
|
-
posts: store.posts || []
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// Generate HTML content directly as a string instead of JSX
|
|
311
|
-
const rootContent = `
|
|
312
|
-
<div style="padding: 20px; max-width: 800px; margin: 0 auto;">
|
|
313
|
-
<h1>Loading Application...</h1>
|
|
314
|
-
<p>The application is initializing. If you continue to see this message, please ensure JavaScript is enabled in your browser.</p>
|
|
315
|
-
</div>
|
|
316
|
-
`;
|
|
317
|
-
|
|
318
|
-
// Create HTML template with properly structured content
|
|
319
|
-
const html = `<!DOCTYPE html>
|
|
320
|
-
<html lang="en">
|
|
321
|
-
<head>
|
|
322
|
-
<meta charset="UTF-8">
|
|
323
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
324
|
-
<title>Frontend Hamroun App</title>
|
|
325
|
-
<!-- Include the processed Tailwind CSS file -->
|
|
326
|
-
<link rel="stylesheet" href="/styles.css">
|
|
327
|
-
<style>
|
|
328
|
-
body {
|
|
329
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
330
|
-
line-height: 1.6;
|
|
331
|
-
color: #333;
|
|
332
|
-
margin: 0;
|
|
333
|
-
padding: 0;
|
|
334
|
-
}
|
|
335
|
-
a {
|
|
336
|
-
color: #0066cc;
|
|
337
|
-
text-decoration: none;
|
|
338
|
-
}
|
|
339
|
-
a:hover {
|
|
340
|
-
text-decoration: underline;
|
|
341
|
-
}
|
|
342
|
-
pre {
|
|
343
|
-
white-space: pre-wrap;
|
|
344
|
-
word-break: break-word;
|
|
345
|
-
}
|
|
346
|
-
</style>
|
|
347
|
-
<script>
|
|
348
|
-
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
|
|
349
|
-
</script>
|
|
350
|
-
</head>
|
|
351
|
-
<body>
|
|
352
|
-
<div id="root">${rootContent}</div>
|
|
353
|
-
<script src="/build/main.js" type="module"></script>
|
|
354
|
-
<script src="/socket.io/socket.io.js"></script>
|
|
355
|
-
</body>
|
|
356
|
-
</html>`;
|
|
357
|
-
|
|
358
|
-
// Send the rendered HTML
|
|
359
|
-
res.send(html);
|
|
360
|
-
} catch (error) {
|
|
361
|
-
console.error('[SSR] Server error:', error);
|
|
362
|
-
next(error);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Error handler
|
|
367
|
-
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
368
|
-
console.error('Server error:', err);
|
|
369
|
-
res.status(500).send(`
|
|
370
|
-
<!DOCTYPE html>
|
|
371
|
-
<html>
|
|
372
|
-
<head><title>Server Error</title></head>
|
|
373
|
-
<body>
|
|
374
|
-
<h1>500 - Server Error</h1>
|
|
375
|
-
<p>${err.message}</p>
|
|
376
|
-
${isDev ? `<pre>${err.stack}</pre>` : ''}
|
|
377
|
-
</body>
|
|
378
|
-
</html>
|
|
379
|
-
`);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// WebSocket setup
|
|
383
|
-
io.on('connection', (socket) => {
|
|
384
|
-
console.log('Client connected:', socket.id);
|
|
385
|
-
|
|
386
|
-
// Send welcome message
|
|
387
|
-
socket.emit('welcome', {
|
|
388
|
-
message: 'Connected to WebSocket server',
|
|
389
|
-
time: new Date().toISOString()
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Handle client messages
|
|
393
|
-
socket.on('message', (data) => {
|
|
394
|
-
console.log('Received message:', data);
|
|
395
|
-
// Broadcast to all clients
|
|
396
|
-
io.emit('broadcast', {
|
|
397
|
-
from: socket.id,
|
|
398
|
-
data,
|
|
399
|
-
time: new Date().toISOString()
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Handle disconnect
|
|
404
|
-
socket.on('disconnect', () => {
|
|
405
|
-
console.log('Client disconnected:', socket.id);
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// Handle file changes for live reload in development
|
|
410
|
-
if (isDev) {
|
|
411
|
-
const { watch } = await import('chokidar');
|
|
412
|
-
const watcher = watch([
|
|
413
|
-
path.join(__dirname, 'src/**/*'),
|
|
414
|
-
path.join(__dirname, 'public/**/*')
|
|
415
|
-
]);
|
|
416
|
-
|
|
417
|
-
watcher.on('change', async (changedPath) => {
|
|
418
|
-
console.log(`[Watcher] File changed: ${changedPath}`);
|
|
419
|
-
|
|
420
|
-
// Rebuild if source files changed
|
|
421
|
-
if (changedPath.startsWith(path.join(__dirname, 'src'))) {
|
|
422
|
-
try {
|
|
423
|
-
await esbuildContext.rebuild();
|
|
424
|
-
console.log('[Watcher] Rebuild complete');
|
|
425
|
-
|
|
426
|
-
// Notify clients to reload
|
|
427
|
-
io.emit('reload');
|
|
428
|
-
} catch (error) {
|
|
429
|
-
console.error('[Watcher] Rebuild failed:', error);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Notify clients of public file changes without rebuild
|
|
434
|
-
if (changedPath.startsWith(path.join(__dirname, 'public'))) {
|
|
435
|
-
io.emit('reload');
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
console.log('[Watcher] File watching enabled for development');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// API routes
|
|
443
|
-
app.get('/api/posts', (req, res) => {
|
|
444
|
-
const { authorId } = req.query;
|
|
445
|
-
|
|
446
|
-
if (authorId) {
|
|
447
|
-
const filteredPosts = store.posts.filter(p => p.authorId === parseInt( authorId as string));
|
|
448
|
-
return res.json(filteredPosts);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
res.json(store.posts);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
// Start the server
|
|
455
|
-
httpServer.listen(PORT, () => {
|
|
456
|
-
console.log(`Server running at http://localhost:${PORT}`);
|
|
457
|
-
console.log(`Server-side rendering enabled with direct esbuild compilation`);
|
|
458
|
-
|
|
459
|
-
if (isDev) {
|
|
460
|
-
console.log(`Development mode with live reload active`);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
// Handle graceful shutdown
|
|
465
|
-
process.on('SIGINT', async () => {
|
|
466
|
-
console.log('Shutting down server...');
|
|
467
|
-
|
|
468
|
-
// Dispose esbuild context if active
|
|
469
|
-
if (esbuildContext) {
|
|
470
|
-
await esbuildContext.dispose();
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
process.exit(0);
|
|
474
|
-
});
|
|
1
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
import { createServer as createHttpServer } from 'http';
|
|
8
|
+
import { Server as SocketServer } from 'socket.io';
|
|
9
|
+
import compression from 'compression';
|
|
10
|
+
import cors from 'cors';
|
|
11
|
+
import * as esbuild from 'esbuild';
|
|
12
|
+
import { renderToString, jsx } from 'baraqex';
|
|
13
|
+
import { App } from './src/App';
|
|
14
|
+
|
|
15
|
+
dotenv.config();
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
|
20
|
+
|
|
21
|
+
const isDev = (process.env.NODE_ENV || 'development') !== 'production';
|
|
22
|
+
|
|
23
|
+
const app = express();
|
|
24
|
+
const httpServer = createHttpServer(app);
|
|
25
|
+
const io = new SocketServer(httpServer, {
|
|
26
|
+
cors: { origin: isDev ? '*' : false, methods: ['GET', 'POST'] }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.use(express.json());
|
|
30
|
+
app.use(express.urlencoded({ extended: true }));
|
|
31
|
+
app.use(compression());
|
|
32
|
+
app.use(cors());
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Sample data store
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const store = {
|
|
38
|
+
users: [
|
|
39
|
+
{ id: 1, name: 'Amina', email: 'amina@example.com' },
|
|
40
|
+
{ id: 2, name: 'Karim', email: 'karim@example.com' },
|
|
41
|
+
{ id: 3, name: 'Sofia', email: 'sofia@example.com' }
|
|
42
|
+
],
|
|
43
|
+
posts: [
|
|
44
|
+
{ id: 1, title: 'Hello Baraqex', authorId: 1 },
|
|
45
|
+
{ id: 2, title: 'SSR is fast', authorId: 2 }
|
|
46
|
+
]
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// API routes (backend)
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
app.get('/api/hello', (req, res) => {
|
|
53
|
+
res.json({ message: 'Hello from the Baraqex API!', time: new Date().toISOString() });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
app.get('/api/users', (req, res) => {
|
|
57
|
+
res.json(store.users);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.get('/api/users/:id', (req, res) => {
|
|
61
|
+
const user = store.users.find((u) => u.id === parseInt(req.params.id, 10));
|
|
62
|
+
if (user) return res.json(user);
|
|
63
|
+
res.status(404).json({ error: 'User not found' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
app.get('/api/posts', (req, res) => {
|
|
67
|
+
const { authorId } = req.query;
|
|
68
|
+
if (authorId) {
|
|
69
|
+
return res.json(store.posts.filter((p) => p.authorId === parseInt(authorId as string, 10)));
|
|
70
|
+
}
|
|
71
|
+
res.json(store.posts);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Static assets: built client bundle + public (incl. WASM output)
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
app.use('/build', express.static(path.join(__dirname, 'build')));
|
|
78
|
+
app.use('/wasm', express.static(path.join(__dirname, 'public', 'wasm')));
|
|
79
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Dev mode: esbuild context (bundles src + rebuilds on change)
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
let esbuildContext: esbuild.BuildContext<any> | null = null;
|
|
85
|
+
|
|
86
|
+
async function setupEsbuild() {
|
|
87
|
+
const buildDir = path.join(__dirname, 'build');
|
|
88
|
+
if (!existsSync(buildDir)) await fs.mkdir(buildDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
esbuildContext = await esbuild.context({
|
|
91
|
+
entryPoints: [path.join(__dirname, 'src', 'main.tsx')],
|
|
92
|
+
bundle: true,
|
|
93
|
+
format: 'esm',
|
|
94
|
+
outdir: buildDir,
|
|
95
|
+
sourcemap: true,
|
|
96
|
+
minify: !isDev,
|
|
97
|
+
jsx: 'transform',
|
|
98
|
+
jsxFactory: 'jsx',
|
|
99
|
+
jsxFragment: 'Fragment',
|
|
100
|
+
loader: { '.tsx': 'tsx', '.ts': 'ts', '.jsx': 'jsx', '.js': 'js', '.css': 'css', '.json': 'json' },
|
|
101
|
+
plugins: [
|
|
102
|
+
{
|
|
103
|
+
name: 'baraqex-jsx',
|
|
104
|
+
setup(build) {
|
|
105
|
+
build.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
|
|
106
|
+
const source = await fs.readFile(args.path, 'utf8');
|
|
107
|
+
const content = `import { jsx, Fragment } from 'baraqex';\n${source}`;
|
|
108
|
+
return { contents: content, loader: args.path.endsWith('tsx') ? 'tsx' : 'jsx' };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await esbuildContext.rebuild();
|
|
117
|
+
if (!isDev) await esbuildContext.dispose();
|
|
118
|
+
console.log(`esbuild: ${isDev ? 'dev' : 'production'} build complete`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Build the SSR Tailwind stylesheet (used by both dev and the built server)
|
|
122
|
+
async function buildStyles() {
|
|
123
|
+
const buildDir = path.join(__dirname, 'build');
|
|
124
|
+
if (!existsSync(buildDir)) await fs.mkdir(buildDir, { recursive: true });
|
|
125
|
+
const postcss = (await import('postcss')).default;
|
|
126
|
+
const tailwindcss = (await import('tailwindcss')).default;
|
|
127
|
+
const autoprefixer = (await import('autoprefixer')).default;
|
|
128
|
+
const css = await fs.readFile(path.join(__dirname, 'src', 'styles.css'), 'utf8');
|
|
129
|
+
const result = await postcss([tailwindcss, autoprefixer]).process(css, { from: path.join(__dirname, 'src', 'styles.css') });
|
|
130
|
+
await fs.writeFile(path.join(buildDir, 'styles.css'), result.css);
|
|
131
|
+
console.log('✅ styles.css built');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build the Tailwind stylesheet used by the SSR-rendered page (both modes)
|
|
135
|
+
await buildStyles();
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// SSR: render the App on the server, then hydrate on the client
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
app.get('*', async (req: Request, res: Response, next: NextFunction) => {
|
|
141
|
+
if (req.path.startsWith('/api/') || req.path.match(/\.(js|css|map|ico|png|jpg|jpeg|gif|svg|woff2?|ttf|eot)$/)) {
|
|
142
|
+
return next();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const route = req.path === '/' ? '/' : req.path;
|
|
147
|
+
const initialState = {
|
|
148
|
+
route,
|
|
149
|
+
serverTime: new Date().toISOString(),
|
|
150
|
+
serverRendered: true,
|
|
151
|
+
users: store.users
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const content = await renderToString(jsx(App, { route, initialState }));
|
|
155
|
+
|
|
156
|
+
const html = `<!DOCTYPE html>
|
|
157
|
+
<html lang="en">
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="UTF-8">
|
|
160
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
161
|
+
<title>Baraqex Full-Stack App (SSR + WASM + Go)</title>
|
|
162
|
+
<link rel="stylesheet" href="/build/styles.css">
|
|
163
|
+
<script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
|
|
164
|
+
</head>
|
|
165
|
+
<body>
|
|
166
|
+
<div id="root">${content}</div>
|
|
167
|
+
<script src="/wasm/wasm_exec.js"></script>
|
|
168
|
+
${isDev ? '<script src="/socket.io/socket.io.js"></script>' : ''}
|
|
169
|
+
<script src="/build/main.js" type="module"></script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>`;
|
|
172
|
+
|
|
173
|
+
res.send(html);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
next(error);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Error handling + start
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
183
|
+
console.error('Server error:', err);
|
|
184
|
+
res.status(500).send(`<pre>${isDev ? err.stack : 'Internal Server Error'}</pre>`);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
io.on('connection', (socket) => {
|
|
188
|
+
console.log('Client connected:', socket.id);
|
|
189
|
+
socket.on('disconnect', () => console.log('Client disconnected:', socket.id));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (isDev) {
|
|
193
|
+
const { watch } = await import('chokidar');
|
|
194
|
+
const watcher = watch([path.join(__dirname, 'src', '**/*'), path.join(__dirname, 'go', '**/*')]);
|
|
195
|
+
|
|
196
|
+
watcher.on('change', async (changedPath) => {
|
|
197
|
+
console.log(`[Watcher] Changed: ${changedPath}`);
|
|
198
|
+
try {
|
|
199
|
+
await esbuildContext?.rebuild();
|
|
200
|
+
io.emit('reload');
|
|
201
|
+
console.log('[Watcher] Rebuild + reload sent');
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('[Watcher] Rebuild failed:', error);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
console.log('[Watcher] Live reload active');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
httpServer.listen(PORT, () => {
|
|
211
|
+
console.log(`Server running at http://localhost:${PORT}`);
|
|
212
|
+
console.log('SSR + REST API + WebAssembly (Go) enabled');
|
|
213
|
+
if (isDev) console.log('Development mode with live reload active');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
process.on('SIGINT', async () => {
|
|
217
|
+
console.log('Shutting down...');
|
|
218
|
+
if (esbuildContext) await esbuildContext.dispose();
|
|
219
|
+
process.exit(0);
|
|
220
|
+
});
|