baraqex 1.0.76 → 2.0.0

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 (182) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +268 -315
  3. package/bin/baraqex.js +150 -12
  4. package/dist/browser.d.ts +1 -1
  5. package/dist/browser.d.ts.map +1 -1
  6. package/dist/browser.js +2319 -205
  7. package/dist/browser.js.map +4 -4
  8. package/dist/core/batch.d.ts +10 -0
  9. package/dist/core/batch.d.ts.map +1 -0
  10. package/dist/core/component.d.ts +69 -0
  11. package/dist/core/component.d.ts.map +1 -0
  12. package/dist/core/context.d.ts +22 -0
  13. package/dist/core/context.d.ts.map +1 -0
  14. package/dist/core/errors.d.ts +125 -0
  15. package/dist/core/errors.d.ts.map +1 -0
  16. package/dist/core/event-bus.d.ts +29 -0
  17. package/dist/core/event-bus.d.ts.map +1 -0
  18. package/dist/core/forms.d.ts +128 -0
  19. package/dist/core/forms.d.ts.map +1 -0
  20. package/dist/core/hooks.d.ts +84 -0
  21. package/dist/core/hooks.d.ts.map +1 -0
  22. package/dist/core/index.d.ts +23 -0
  23. package/dist/core/index.d.ts.map +1 -0
  24. package/dist/core/jsx-runtime.d.ts +45 -0
  25. package/dist/core/jsx-runtime.d.ts.map +1 -0
  26. package/dist/core/lifecycle-events.d.ts +71 -0
  27. package/dist/core/lifecycle-events.d.ts.map +1 -0
  28. package/dist/core/renderer.d.ts +39 -0
  29. package/dist/core/renderer.d.ts.map +1 -0
  30. package/dist/core/router.d.ts +108 -0
  31. package/dist/core/router.d.ts.map +1 -0
  32. package/dist/core/server-renderer.d.ts +26 -0
  33. package/dist/core/server-renderer.d.ts.map +1 -0
  34. package/dist/core/store.d.ts +102 -0
  35. package/dist/core/store.d.ts.map +1 -0
  36. package/dist/core/types.d.ts +66 -0
  37. package/dist/core/types.d.ts.map +1 -0
  38. package/dist/core/utils.d.ts +153 -0
  39. package/dist/core/utils.d.ts.map +1 -0
  40. package/dist/core/vdom.d.ts +29 -0
  41. package/dist/core/vdom.d.ts.map +1 -0
  42. package/dist/index.d.ts +39 -35
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2592 -508
  45. package/dist/index.js.map +4 -4
  46. package/dist/server/auth.d.ts +8 -3
  47. package/dist/server/auth.d.ts.map +1 -1
  48. package/dist/server/index.d.ts +2 -2
  49. package/dist/server/index.d.ts.map +1 -1
  50. package/dist/server/index.js +173 -112
  51. package/dist/server/index.js.map +4 -4
  52. package/dist/server/middleware.d.ts +10 -1
  53. package/dist/server/middleware.d.ts.map +1 -1
  54. package/package.json +224 -124
  55. package/templates/basic-app/src/main.jsx +336 -0
  56. package/templates/complete-app/api/hello.js +0 -0
  57. package/templates/complete-app/client.js +58 -0
  58. package/templates/complete-app/lib/frontend-hamroun.js +182 -0
  59. package/templates/complete-app/package.json +18 -0
  60. package/templates/complete-app/pages/about.js +119 -0
  61. package/templates/complete-app/pages/about.jsx +0 -0
  62. package/templates/complete-app/pages/index.js +157 -0
  63. package/templates/complete-app/pages/index.jsx +0 -0
  64. package/templates/complete-app/pages/wasm-demo.js +290 -0
  65. package/templates/complete-app/pages/wasm-demo.jsx +0 -0
  66. package/templates/complete-app/public/client.js +89 -0
  67. package/templates/complete-app/public/index.html +118 -0
  68. package/templates/complete-app/public/styles.css +76 -0
  69. package/templates/complete-app/readme.md +188 -0
  70. package/templates/complete-app/server.js +226 -0
  71. package/templates/complete-app/server.ts +275 -0
  72. package/templates/complete-app/src/App.tsx +59 -0
  73. package/templates/complete-app/src/client.ts +61 -0
  74. package/templates/complete-app/src/client.tsx +18 -0
  75. package/templates/complete-app/src/pages/index.tsx +51 -0
  76. package/templates/complete-app/src/server.ts +218 -0
  77. package/templates/complete-app/tsconfig.json +22 -0
  78. package/templates/complete-app/tsconfig.server.json +19 -0
  79. package/templates/complete-app/vite.config.js +57 -0
  80. package/templates/complete-app/vite.config.ts +30 -0
  81. package/templates/complete-app/wasm/build.bat +0 -0
  82. package/templates/complete-app/wasm/build.sh +0 -0
  83. package/templates/complete-app/wasm/example.go +0 -0
  84. package/templates/fullstack-app/README.md +37 -0
  85. package/templates/fullstack-app/api/hello.js +11 -0
  86. package/templates/fullstack-app/api/hello.ts +18 -0
  87. package/templates/fullstack-app/api/users/[id].ts +73 -0
  88. package/templates/fullstack-app/api/users/index.ts +32 -0
  89. package/templates/fullstack-app/build/main.css +874 -0
  90. package/templates/fullstack-app/build/main.css.map +7 -0
  91. package/templates/fullstack-app/build/main.js +967 -0
  92. package/templates/fullstack-app/build/main.js.map +7 -0
  93. package/templates/fullstack-app/build.ts +210 -0
  94. package/templates/fullstack-app/index.html +36 -0
  95. package/templates/fullstack-app/package.json +35 -0
  96. package/templates/fullstack-app/postcss.config.js +6 -0
  97. package/templates/fullstack-app/process-tailwind.js +45 -0
  98. package/templates/fullstack-app/public/_redirects +1 -0
  99. package/templates/fullstack-app/public/route-handler.js +47 -0
  100. package/templates/fullstack-app/public/spa-fix.html +17 -0
  101. package/templates/fullstack-app/public/styles.css +975 -0
  102. package/templates/fullstack-app/public/tailwind.css +15 -0
  103. package/templates/fullstack-app/server.js +267 -0
  104. package/templates/fullstack-app/server.ts +474 -0
  105. package/templates/fullstack-app/src/README.md +55 -0
  106. package/templates/fullstack-app/src/client.js +87 -0
  107. package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
  108. package/templates/fullstack-app/src/components/ErrorBoundary.tsx +36 -0
  109. package/templates/fullstack-app/src/components/Layout.tsx +42 -0
  110. package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
  111. package/templates/fullstack-app/src/components/UserList.tsx +44 -0
  112. package/templates/fullstack-app/src/config.ts +42 -0
  113. package/templates/fullstack-app/src/data/api.ts +206 -0
  114. package/templates/fullstack-app/src/main.tsx +155 -0
  115. package/templates/fullstack-app/src/middleware.ts +28 -0
  116. package/templates/fullstack-app/src/pages/404.tsx +28 -0
  117. package/templates/fullstack-app/src/pages/[id].tsx +0 -0
  118. package/templates/fullstack-app/src/pages/_app.tsx +11 -0
  119. package/templates/fullstack-app/src/pages/_document.tsx +25 -0
  120. package/templates/fullstack-app/src/pages/_error.tsx +45 -0
  121. package/templates/fullstack-app/src/pages/about/index.tsx +67 -0
  122. package/templates/fullstack-app/src/pages/about.tsx +71 -0
  123. package/templates/fullstack-app/src/pages/api/users/[id].ts +73 -0
  124. package/templates/fullstack-app/src/pages/api/users/index.ts +43 -0
  125. package/templates/fullstack-app/src/pages/index.tsx +110 -0
  126. package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
  127. package/templates/fullstack-app/src/pages/users.tsx +60 -0
  128. package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
  129. package/templates/fullstack-app/src/router.ts +255 -0
  130. package/templates/fullstack-app/src/styles.css +5 -0
  131. package/templates/fullstack-app/tailwind.config.js +11 -0
  132. package/templates/fullstack-app/tsconfig.json +18 -0
  133. package/templates/fullstack-app/vite.config.js +87 -0
  134. package/templates/go/build.bat +36 -0
  135. package/templates/go/build.sh +43 -0
  136. package/templates/go/example.go +205 -0
  137. package/templates/go-wasm-app/README.md +38 -0
  138. package/templates/go-wasm-app/babel.config.js +21 -0
  139. package/templates/go-wasm-app/build-client.js +49 -0
  140. package/templates/go-wasm-app/build-wasm.js +237 -0
  141. package/templates/go-wasm-app/build.config.js +62 -0
  142. package/templates/go-wasm-app/build.js +218 -0
  143. package/templates/go-wasm-app/package.json +32 -0
  144. package/templates/go-wasm-app/public/index.html +128 -0
  145. package/templates/go-wasm-app/public/styles.css +197 -0
  146. package/templates/go-wasm-app/public/wasm/example.wasm +0 -0
  147. package/templates/go-wasm-app/public/wasm/wasm_exec.js +561 -0
  148. package/templates/go-wasm-app/public/wasm/wasm_exec_node.js +39 -0
  149. package/templates/go-wasm-app/server.js +70 -0
  150. package/templates/go-wasm-app/src/App.jsx +38 -0
  151. package/templates/go-wasm-app/src/app.js +173 -0
  152. package/templates/go-wasm-app/src/client.js +57 -0
  153. package/templates/go-wasm-app/src/components/Footer.jsx +13 -0
  154. package/templates/go-wasm-app/src/components/Header.jsx +19 -0
  155. package/templates/go-wasm-app/src/components/WasmDemo.jsx +120 -0
  156. package/templates/go-wasm-app/src/main.jsx +12 -0
  157. package/templates/go-wasm-app/src/wasm/example.go +75 -0
  158. package/templates/go-wasm-app/tsconfig.server.json +18 -0
  159. package/templates/go-wasm-app/vite.config.js +45 -0
  160. package/dist/forms/index.d.ts +0 -2
  161. package/dist/forms/index.d.ts.map +0 -1
  162. package/dist/forms/types.d.ts +0 -2
  163. package/dist/forms/types.d.ts.map +0 -1
  164. package/dist/frontend/components/WasmLoader.d.ts +0 -2
  165. package/dist/frontend/components/WasmLoader.d.ts.map +0 -1
  166. package/dist/frontend/hooks/useWasm.d.ts +0 -2
  167. package/dist/frontend/hooks/useWasm.d.ts.map +0 -1
  168. package/dist/frontend/index.d.ts +0 -2
  169. package/dist/frontend/index.d.ts.map +0 -1
  170. package/dist/router/index.d.ts +0 -2
  171. package/dist/router/index.d.ts.map +0 -1
  172. package/dist/router/types.d.ts +0 -2
  173. package/dist/router/types.d.ts.map +0 -1
  174. package/dist/store/index.d.ts +0 -2
  175. package/dist/store/index.d.ts.map +0 -1
  176. package/dist/store/types.d.ts +0 -2
  177. package/dist/store/types.d.ts.map +0 -1
  178. package/templates/basic-app/package-lock.json +0 -7173
  179. package/templates/fullstack-template/package-lock.json +0 -3558
  180. package/templates/ssr-template/package-lock.json +0 -5380
  181. package/templates/wasm/package-lock.json +0 -5347
  182. package/templates/wasm/public/example.wasm +0 -0
@@ -0,0 +1,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, 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
+ });
@@ -0,0 +1,55 @@
1
+ # Frontend Hamroun Fullstack Template
2
+
3
+ This template uses a file-system based routing system similar to Next.js. Here's how to use it:
4
+
5
+ ## Creating New Pages
6
+
7
+ 1. Add new pages in the `src/pages` directory
8
+ 2. File names become routes automatically:
9
+ - `src/pages/index.tsx` → `/`
10
+ - `src/pages/about.tsx` → `/about`
11
+ - `src/pages/users/[id].tsx` → `/users/:id`
12
+
13
+ ## Page Structure
14
+
15
+ Each page should follow this structure:
16
+
17
+ ```tsx
18
+ import { jsx } from 'frontend-hamroun';
19
+ import Layout from '../components/Layout';
20
+
21
+ const YourPage = ({ initialState }) => (
22
+ <Layout title="Your Page Title">
23
+ {/* Your content here */}
24
+ </Layout>
25
+ );
26
+
27
+ // Optional: Add data fetching
28
+ YourPage.getInitialData = async (path) => {
29
+ // Fetch any data your page needs
30
+ return {
31
+ yourData: await fetchSomething()
32
+ };
33
+ };
34
+
35
+ export default YourPage;
36
+ ```
37
+
38
+ ## Data Fetching
39
+
40
+ Use the API utilities in `src/data/api.ts` to fetch data in a consistent way:
41
+
42
+ ```tsx
43
+ import { UserApi } from '../data/api';
44
+
45
+ // In your getInitialData method:
46
+ const users = await UserApi.getAll();
47
+ ```
48
+
49
+ ## Components
50
+
51
+ Store reusable components in the `src/components` directory and import them in your pages.
52
+
53
+ ## Navigation
54
+
55
+ Client-side navigation is handled automatically. Just use regular `<a href="...">` links.
@@ -0,0 +1,87 @@
1
+ import { hydrate, createElement, jsx } from 'frontend-hamroun';
2
+
3
+ // Add global jsx for compatibility
4
+ window.jsx = jsx;
5
+ window.createElement = createElement;
6
+
7
+ // Get the initial state from the server (if available)
8
+ const initialState = window.__INITIAL_STATE__ || {
9
+ route: window.location.pathname
10
+ };
11
+
12
+ // Dynamically import the right page component based on the route
13
+ async function loadAndHydrateComponent() {
14
+ try {
15
+ const path = initialState.route || '/';
16
+ const normalizedPath = path === '/' ? '/index' : path;
17
+
18
+ console.log(`Loading component for path: ${normalizedPath}`);
19
+
20
+ // Try to import the page component
21
+ const module = await import(`./pages${normalizedPath}.js`).catch(() =>
22
+ import(`./pages${normalizedPath}/index.js`)).catch(() => {
23
+ console.warn(`No component found for ${normalizedPath}, falling back to index`);
24
+ return import('./pages/index.js');
25
+ });
26
+
27
+ const PageComponent = module.default;
28
+
29
+ if (!PageComponent) {
30
+ throw new Error(`No default export found in module for ${normalizedPath}`);
31
+ }
32
+
33
+ // Find the root element - try both 'root' and 'app' IDs
34
+ const rootElement = document.getElementById('root') || document.getElementById('app');
35
+
36
+ if (!rootElement) {
37
+ throw new Error('Could not find root element with id "root" or "app"');
38
+ }
39
+
40
+ // Hydrate the application
41
+ hydrate(jsx(PageComponent, { initialState }), rootElement);
42
+ console.log('Hydration complete');
43
+
44
+ // Add navigation event listeners if using client-side routing
45
+ document.addEventListener('click', handleLinkClicks);
46
+ window.addEventListener('popstate', handleRouteChange);
47
+
48
+ return true;
49
+ } catch (error) {
50
+ console.error('Error during hydration:', error);
51
+ return false;
52
+ }
53
+ }
54
+
55
+ // Handle client-side navigation for links
56
+ function handleLinkClicks(event) {
57
+ // Only handle links within our app
58
+ if (event.target.tagName === 'A' &&
59
+ event.target.origin === window.location.origin &&
60
+ !event.target.hasAttribute('external')) {
61
+
62
+ event.preventDefault();
63
+ const href = event.target.getAttribute('href');
64
+
65
+ // Update history and load the new component
66
+ window.history.pushState({}, '', href);
67
+ handleRouteChange();
68
+ }
69
+ }
70
+
71
+ // Handle route changes (back/forward navigation or pushState)
72
+ function handleRouteChange() {
73
+ // Update the initialState with the new route
74
+ initialState.route = window.location.pathname;
75
+
76
+ // Load and hydrate the new component
77
+ loadAndHydrateComponent().catch(err => {
78
+ console.error('Failed to load component after route change:', err);
79
+ });
80
+ }
81
+
82
+ // Wait for DOM to be ready before hydrating
83
+ if (document.readyState === 'loading') {
84
+ document.addEventListener('DOMContentLoaded', loadAndHydrateComponent);
85
+ } else {
86
+ loadAndHydrateComponent();
87
+ }
@@ -0,0 +1,36 @@
1
+ import { jsx, useState, useEffect, useErrorBoundary } from 'frontend-hamroun';
2
+
3
+ interface ErrorBoundaryProps {
4
+ children: any;
5
+ fallback?: (error: Error, reset: () => void) => any;
6
+ }
7
+
8
+ export function ErrorBoundary({ children, fallback }: ErrorBoundaryProps) {
9
+ const [error, resetError] = useErrorBoundary();
10
+
11
+ if (error) {
12
+ if (fallback) {
13
+ return fallback(error, resetError);
14
+ }
15
+
16
+ return (
17
+ <div className="error-boundary p-4 border border-red-500 rounded bg-red-50">
18
+ <h3 className="text-lg font-medium text-red-800 mb-2">Something went wrong</h3>
19
+ <p className="text-red-600 mb-2">{error.message}</p>
20
+ <button
21
+ onClick={resetError}
22
+ className="px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700"
23
+ >
24
+ Try again
25
+ </button>
26
+ {process.env.NODE_ENV !== 'production' && (
27
+ <pre className="mt-3 text-xs overflow-auto p-2 bg-gray-100">
28
+ {error.stack}
29
+ </pre>
30
+ )}
31
+ </div>
32
+ );
33
+ }
34
+
35
+ return children;
36
+ }
@@ -0,0 +1,42 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+
3
+ interface LayoutProps {
4
+ children: any;
5
+ title?: string;
6
+ }
7
+
8
+ export default function Layout({ children, title = 'Frontend Hamroun App' }: LayoutProps) {
9
+ return (
10
+ <div className="min-h-screen flex flex-col bg-gray-50">
11
+ <header className="bg-blue-600 text-white">
12
+ <div className="container mx-auto px-4 py-4 flex justify-between items-center">
13
+ <h1 className="text-xl font-bold">
14
+ <a href="/" className="hover:text-blue-100">
15
+ {title}
16
+ </a>
17
+ </h1>
18
+
19
+ <nav>
20
+ <ul className="flex space-x-6">
21
+ <li><a href="/" className="hover:text-blue-100">Home</a></li>
22
+ <li><a href="/about" className="hover:text-blue-100">About</a></li>
23
+ {/* WASM demo link removed */}
24
+ </ul>
25
+ </nav>
26
+ </div>
27
+ </header>
28
+
29
+ <main className="flex-grow container mx-auto px-4 py-8">
30
+ {children}
31
+ </main>
32
+
33
+ <footer className="bg-gray-800 text-white">
34
+ <div className="container mx-auto px-4 py-6">
35
+ <p className="text-center text-gray-400">
36
+ &copy; {new Date().getFullYear()} Frontend Hamroun App. All rights reserved.
37
+ </p>
38
+ </div>
39
+ </footer>
40
+ </div>
41
+ );
42
+ }