baraqex 2.0.3 → 2.0.5

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 (65) hide show
  1. package/dist/browser.js +11 -9
  2. package/dist/browser.js.map +2 -2
  3. package/dist/core/jsx-runtime.d.ts +1 -1
  4. package/dist/core/jsx-runtime.d.ts.map +1 -1
  5. package/dist/index.js +11 -9
  6. package/dist/index.js.map +2 -2
  7. package/package.json +1 -1
  8. package/templates/fullstack-app/README.md +62 -37
  9. package/templates/fullstack-app/build-wasm.js +80 -0
  10. package/templates/fullstack-app/build.mjs +133 -0
  11. package/templates/fullstack-app/go/main.go +53 -0
  12. package/templates/fullstack-app/index.html +17 -36
  13. package/templates/fullstack-app/package.json +41 -35
  14. package/templates/fullstack-app/postcss.config.js +6 -6
  15. package/templates/fullstack-app/public/wasm/.gitkeep +1 -0
  16. package/templates/fullstack-app/server.ts +239 -474
  17. package/templates/fullstack-app/src/App.tsx +77 -0
  18. package/templates/fullstack-app/src/main.tsx +31 -155
  19. package/templates/fullstack-app/src/pages/About.tsx +40 -0
  20. package/templates/fullstack-app/src/pages/Home.tsx +67 -0
  21. package/templates/fullstack-app/src/pages/WasmDemo.tsx +81 -0
  22. package/templates/fullstack-app/src/styles.css +12 -5
  23. package/templates/fullstack-app/tailwind.config.js +8 -11
  24. package/templates/fullstack-app/tsconfig.json +20 -18
  25. package/templates/fullstack-app/api/hello.js +0 -11
  26. package/templates/fullstack-app/api/hello.ts +0 -18
  27. package/templates/fullstack-app/api/users/[id].ts +0 -73
  28. package/templates/fullstack-app/api/users/index.ts +0 -32
  29. package/templates/fullstack-app/build/main.css +0 -874
  30. package/templates/fullstack-app/build/main.css.map +0 -7
  31. package/templates/fullstack-app/build/main.js +0 -967
  32. package/templates/fullstack-app/build/main.js.map +0 -7
  33. package/templates/fullstack-app/build.ts +0 -210
  34. package/templates/fullstack-app/process-tailwind.js +0 -45
  35. package/templates/fullstack-app/public/_redirects +0 -1
  36. package/templates/fullstack-app/public/route-handler.js +0 -47
  37. package/templates/fullstack-app/public/spa-fix.html +0 -17
  38. package/templates/fullstack-app/public/styles.css +0 -975
  39. package/templates/fullstack-app/public/tailwind.css +0 -15
  40. package/templates/fullstack-app/server.js +0 -267
  41. package/templates/fullstack-app/src/README.md +0 -55
  42. package/templates/fullstack-app/src/client.js +0 -87
  43. package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
  44. package/templates/fullstack-app/src/components/ErrorBoundary.tsx +0 -36
  45. package/templates/fullstack-app/src/components/Layout.tsx +0 -42
  46. package/templates/fullstack-app/src/components/StateDemo.tsx +0 -207
  47. package/templates/fullstack-app/src/components/UserList.tsx +0 -44
  48. package/templates/fullstack-app/src/config.ts +0 -42
  49. package/templates/fullstack-app/src/data/api.ts +0 -206
  50. package/templates/fullstack-app/src/middleware.ts +0 -28
  51. package/templates/fullstack-app/src/pages/404.tsx +0 -28
  52. package/templates/fullstack-app/src/pages/[id].tsx +0 -0
  53. package/templates/fullstack-app/src/pages/_app.tsx +0 -11
  54. package/templates/fullstack-app/src/pages/_document.tsx +0 -25
  55. package/templates/fullstack-app/src/pages/_error.tsx +0 -45
  56. package/templates/fullstack-app/src/pages/about/index.tsx +0 -67
  57. package/templates/fullstack-app/src/pages/about.tsx +0 -71
  58. package/templates/fullstack-app/src/pages/api/users/[id].ts +0 -73
  59. package/templates/fullstack-app/src/pages/api/users/index.ts +0 -43
  60. package/templates/fullstack-app/src/pages/index.tsx +0 -110
  61. package/templates/fullstack-app/src/pages/users/[id].tsx +0 -153
  62. package/templates/fullstack-app/src/pages/users.tsx +0 -60
  63. package/templates/fullstack-app/src/pages/wasm-demo.tsx +0 -1
  64. package/templates/fullstack-app/src/router.ts +0 -255
  65. package/templates/fullstack-app/vite.config.js +0 -87
@@ -1,474 +1,239 @@
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, PathLike } from 'fs';
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
- // Setup environment
14
- dotenv.config();
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = path.dirname(__filename);
17
-
18
- // Create Express app
19
- const app = express();
20
- const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
21
- const httpServer = createServer(app);
22
-
23
- // Set development mode
24
- if (process.env.NODE_ENV === undefined) {
25
- process.env.NODE_ENV = 'development';
26
- }
27
- const isDev = process.env.NODE_ENV !== 'production';
28
-
29
- // Create socket.io server
30
- const io = new SocketServer(httpServer, {
31
- cors: {
32
- origin: isDev ? '*' : false,
33
- methods: ['GET', 'POST']
34
- }
35
- });
36
-
37
- // Middleware setup
38
- app.use(express.json());
39
- app.use(express.urlencoded({ extended: true }));
40
- app.use(compression());
41
- app.use(cors());
42
- if (isDev) {
43
- app.use((req, res, next) => {
44
- console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
45
- next();
46
- });
47
- }
48
-
49
- // Sample data store
50
- const store = {
51
- users: [
52
- { id: 1, name: 'User 1', email: 'user1@example.com' },
53
- { id: 2, name: 'User 2', email: 'user2@example.com' },
54
- { id: 3, name: 'User 3', email: 'user3@example.com' }
55
- ],
56
- posts: [
57
- { id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
58
- { id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
59
- { id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
60
- ]
61
- };
62
-
63
- // API Routes
64
- app.get('/api/hello', (req, res) => {
65
- res.json({
66
- message: 'Hello from the API!',
67
- time: new Date().toISOString()
68
- });
69
- });
70
-
71
- app.get('/api/users', (req, res) => {
72
- res.json(store.users);
73
- });
74
-
75
- app.get('/api/users/:id', (req, res) => {
76
- const user = store.users.find(u => u.id === parseInt(req.params.id));
77
- if (user) {
78
- res.json(user);
79
- } else {
80
- res.status(404).json({ error: 'User not found' });
81
- }
82
- });
83
-
84
- // Setup esbuild
85
- let esbuildContext: esbuild.BuildContext<{ entryPoints: string[]; bundle: true; format: "esm"; outdir: string; sourcemap: boolean; minify: boolean; jsx: "transform"; jsxFactory: string; jsxFragment: string; external: string[]; define: { 'process.env.NODE_ENV': string; }; loader: { '.tsx': "tsx"; '.ts': "ts"; '.jsx': "jsx"; '.js': "js"; '.css': "css"; '.json': "json"; '.png': "file"; '.jpg': "file"; '.svg': "file"; }; plugins: { name: string; setup(build: esbuild.PluginBuild): void; }[]; }>;
86
-
87
- async function setupEsbuild() {
88
- const buildDir = path.join(__dirname, 'build');
89
- if (!existsSync(buildDir)) {
90
- await fs.mkdir(buildDir, { recursive: true });
91
- }
92
-
93
- // Import PostCSS, Tailwind and Autoprefixer when in development mode
94
- let postcssPlugin = null;
95
- if (isDev) {
96
- try {
97
- const postcss = (await import('postcss')).default;
98
- const tailwindcss = (await import('tailwindcss')).default;
99
- const autoprefixer = (await import('autoprefixer')).default;
100
-
101
- // Create a plugin to process CSS with Tailwind
102
- postcssPlugin = {
103
- name: 'postcss-tailwind',
104
- setup(build: esbuild.PluginBuild) {
105
- build.onLoad({ filter: /\.css$/ }, async (args) => {
106
- const css = await fs.readFile(args.path, 'utf8');
107
- try {
108
- // Process CSS with PostCSS + Tailwind
109
- const result = await postcss([
110
- tailwindcss,
111
- autoprefixer
112
- ]).process(css, {
113
- from: args.path,
114
- to: path.join(buildDir, 'styles.css')
115
- });
116
-
117
- return {
118
- contents: result.css,
119
- loader: 'css' as esbuild.Loader
120
- };
121
- } catch (error) {
122
- console.error('Error processing CSS with Tailwind:', error);
123
- return { contents: css, loader: 'css' as esbuild.Loader };
124
- }
125
- });
126
- }
127
- };
128
- } catch (error) {
129
- console.warn('PostCSS plugins not available, CSS will not be processed with Tailwind:', error);
130
- }
131
- }
132
-
133
- esbuildContext = await esbuild.context({
134
- entryPoints: [
135
- path.join(__dirname, 'src', 'main.tsx'),
136
- ],
137
- bundle: true,
138
- format: 'esm',
139
- outdir: buildDir,
140
- sourcemap: isDev,
141
- minify: !isDev,
142
- jsx: 'transform',
143
- jsxFactory: 'jsx',
144
- jsxFragment: 'Fragment',
145
- external: ['react', 'react/jsx-runtime', 'react-dom'],
146
- define: {
147
- 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
148
- },
149
- loader: {
150
- '.tsx': 'tsx',
151
- '.ts': 'ts',
152
- '.jsx': 'jsx',
153
- '.js': 'js',
154
- '.css': 'css',
155
- '.json': 'json',
156
- '.png': 'file',
157
- '.jpg': 'file',
158
- '.svg': 'file'
159
- },
160
- plugins: [
161
- {
162
- name: 'jsx-runtime-import',
163
- setup(build) {
164
- build.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
165
- const source = await fs.readFile(args.path, 'utf8');
166
- const content = `import { jsx, Fragment } from 'frontend-hamroun';\n${source}`;
167
- return { contents: content, loader: args.path.endsWith('tsx') ? 'tsx' : 'jsx' };
168
- });
169
- }
170
- },
171
- ...(postcssPlugin ? [postcssPlugin] : [])
172
- ]
173
- });
174
-
175
- if (isDev) {
176
- await esbuildContext.rebuild();
177
- console.log('esbuild: Initial build complete');
178
- } else {
179
- await esbuildContext.rebuild();
180
- await esbuildContext.dispose();
181
- console.log('esbuild: Production build complete');
182
- }
183
- }
184
-
185
- // Initialize esbuild
186
- setupEsbuild().catch(err => {
187
- console.error('Failed to setup esbuild:', err);
188
- process.exit(1);
189
- });
190
-
191
- // Serve static files
192
- app.use('/build', express.static(path.join(__dirname, 'build')));
193
- app.use(express.static(path.join(__dirname, 'public')));
194
-
195
- // Development mode file serving
196
- if (isDev) {
197
- app.get('/src/*', async (req, res, next) => {
198
- try {
199
- const filePath = path.join(__dirname, req.path);
200
-
201
- // Check if file exists
202
- if (!existsSync(filePath)) {
203
- return next();
204
- }
205
-
206
- // Read the file
207
- const source = await fs.readFile(filePath, 'utf8');
208
-
209
- // Determine content type based on file extension
210
- const ext = path.extname(filePath);
211
- let contentType = 'application/javascript';
212
- let loader = 'js';
213
-
214
- switch (ext) {
215
- case '.ts':
216
- loader = 'ts';
217
- break;
218
- case '.tsx':
219
- loader = 'tsx';
220
- break;
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: Request, res: Response) => {
61
+ const user = store.users.find((u) => u.id === parseInt(req.params.id, 10));
62
+ if (user) {
63
+ res.json(user);
64
+ return;
65
+ }
66
+ res.status(404).json({ error: 'User not found' });
67
+ });
68
+
69
+ app.get('/api/posts', (req: Request, res: Response) => {
70
+ const { authorId } = req.query;
71
+ if (authorId) {
72
+ res.json(store.posts.filter((p) => p.authorId === parseInt(authorId as string, 10)));
73
+ return;
74
+ }
75
+ res.json(store.posts);
76
+ });
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Static assets: built client bundle + public (incl. WASM output)
80
+ // ---------------------------------------------------------------------------
81
+ app.use('/build', express.static(path.join(__dirname, 'build')));
82
+ app.use('/wasm', express.static(path.join(__dirname, 'public', 'wasm')));
83
+ app.use(express.static(path.join(__dirname, 'public')));
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Dev mode: esbuild context (bundles src + rebuilds on change)
87
+ // ---------------------------------------------------------------------------
88
+ let esbuildContext: esbuild.BuildContext<any> | null = null;
89
+
90
+ async function setupEsbuild() {
91
+ const buildDir = path.join(__dirname, 'build');
92
+ if (!existsSync(buildDir)) await fs.mkdir(buildDir, { recursive: true });
93
+
94
+ esbuildContext = await esbuild.context({
95
+ entryPoints: [path.join(__dirname, 'src', 'main.tsx')],
96
+ bundle: true,
97
+ format: 'esm',
98
+ outdir: buildDir,
99
+ sourcemap: true,
100
+ minify: !isDev,
101
+ jsx: 'transform',
102
+ jsxFactory: 'jsx',
103
+ jsxFragment: 'Fragment',
104
+ loader: { '.tsx': 'tsx', '.ts': 'ts', '.jsx': 'jsx', '.js': 'js', '.css': 'css', '.json': 'json' },
105
+ plugins: [
106
+ {
107
+ name: 'baraqex-jsx',
108
+ setup(build) {
109
+ build.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
110
+ const source = await fs.readFile(args.path, 'utf8');
111
+ const content = `import { jsx, Fragment } from 'baraqex';\n${source}`;
112
+ return { contents: content, loader: args.path.endsWith('tsx') ? 'tsx' : 'jsx' };
113
+ });
114
+ }
115
+ }
116
+ ],
117
+ define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') }
118
+ });
119
+
120
+ await esbuildContext.rebuild();
121
+ if (!isDev) await esbuildContext.dispose();
122
+ console.log(`esbuild: ${isDev ? 'dev' : 'production'} build complete`);
123
+ }
124
+
125
+ // Build the SSR Tailwind stylesheet (used by both dev and the built server)
126
+ async function buildStyles() {
127
+ const buildDir = path.join(__dirname, 'build');
128
+ if (!existsSync(buildDir)) await fs.mkdir(buildDir, { recursive: true });
129
+ const postcss = (await import('postcss')).default;
130
+ const tailwindcss = (await import('tailwindcss')).default;
131
+ const autoprefixer = (await import('autoprefixer')).default;
132
+ const css = await fs.readFile(path.join(__dirname, 'src', 'styles.css'), 'utf8');
133
+ const result = await postcss([tailwindcss, autoprefixer]).process(css, { from: path.join(__dirname, 'src', 'styles.css') });
134
+ await fs.writeFile(path.join(buildDir, 'styles.css'), result.css);
135
+ console.log('✅ styles.css built');
136
+ }
137
+
138
+ // Build the Tailwind stylesheet used by the SSR-rendered page (both modes)
139
+ await buildStyles();
140
+
141
+ // In dev, bundle the client with esbuild (rebuilds on change via the watcher below)
142
+ if (isDev) {
143
+ await setupEsbuild();
144
+ }
145
+
146
+ // Only load the Go runtime scripts when the WASM module has been built
147
+ // (`npm run build:wasm` requires Go). Off by default so dev works without Go.
148
+ const hasWasm = existsSync(path.join(__dirname, 'public', 'wasm', 'example.wasm'))
149
+ && existsSync(path.join(__dirname, 'public', 'wasm', 'wasm_exec.js'));
150
+
151
+ const wasmScripts = hasWasm
152
+ ? '<script src="/wasm/wasm_exec.js"></script>'
153
+ : '<script>console.warn("Go WASM not built — run `npm run build:wasm` (requires Go)");</script>';
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // SSR: render the App on the server, then hydrate on the client
157
+ // ---------------------------------------------------------------------------
158
+ app.get('*', async (req: Request, res: Response, next: NextFunction) => {
159
+ if (req.path.startsWith('/api/') || req.path.match(/\.(js|css|map|ico|png|jpg|jpeg|gif|svg|woff2?|ttf|eot)$/)) {
160
+ return next();
161
+ }
162
+
163
+ try {
164
+ const route = req.path === '/' ? '/' : req.path;
165
+ const initialState = {
166
+ route,
167
+ serverTime: new Date().toISOString(),
168
+ serverRendered: true,
169
+ users: store.users
170
+ };
171
+
172
+ const content = await renderToString(jsx(App, { route, initialState }));
173
+
174
+ const html = `<!DOCTYPE html>
175
+ <html lang="en">
176
+ <head>
177
+ <meta charset="UTF-8">
178
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
179
+ <title>Baraqex Full-Stack App (SSR + WASM + Go)</title>
180
+ <link rel="stylesheet" href="/build/styles.css">
181
+ <link rel="icon" href="data:,">
182
+ <script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
183
+ </head>
184
+ <body>
185
+ <div id="root">${content}</div>
186
+ ${wasmScripts}
187
+ ${isDev ? '<script src="/socket.io/socket.io.js"></script>' : ''}
188
+ <script src="/build/main.js" type="module"></script>
189
+ </body>
190
+ </html>`;
191
+
192
+ res.send(html);
193
+ } catch (error) {
194
+ next(error);
195
+ }
196
+ });
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Error handling + start
200
+ // ---------------------------------------------------------------------------
201
+ app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
202
+ console.error('Server error:', err);
203
+ res.status(500).send(`<pre>${isDev ? err.stack : 'Internal Server Error'}</pre>`);
204
+ });
205
+
206
+ io.on('connection', (socket) => {
207
+ console.log('Client connected:', socket.id);
208
+ socket.on('disconnect', () => console.log('Client disconnected:', socket.id));
209
+ });
210
+
211
+ if (isDev) {
212
+ const { watch } = await import('chokidar');
213
+ const watcher = watch([path.join(__dirname, 'src', '**/*'), path.join(__dirname, 'go', '**/*')]);
214
+
215
+ watcher.on('change', async (changedPath) => {
216
+ console.log(`[Watcher] Changed: ${changedPath}`);
217
+ try {
218
+ await esbuildContext?.rebuild();
219
+ io.emit('reload');
220
+ console.log('[Watcher] Rebuild + reload sent');
221
+ } catch (error) {
222
+ console.error('[Watcher] Rebuild failed:', error);
223
+ }
224
+ });
225
+
226
+ console.log('[Watcher] Live reload active');
227
+ }
228
+
229
+ httpServer.listen(PORT, () => {
230
+ console.log(`Server running at http://localhost:${PORT}`);
231
+ console.log('SSR + REST API + WebAssembly (Go) enabled');
232
+ if (isDev) console.log('Development mode with live reload active');
233
+ });
234
+
235
+ process.on('SIGINT', async () => {
236
+ console.log('Shutting down...');
237
+ if (esbuildContext) await esbuildContext.dispose();
238
+ process.exit(0);
239
+ });