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.
- package/CHANGELOG.md +73 -0
- package/README.md +268 -315
- package/bin/baraqex.js +150 -12
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2319 -205
- package/dist/browser.js.map +4 -4
- package/dist/core/batch.d.ts +10 -0
- package/dist/core/batch.d.ts.map +1 -0
- package/dist/core/component.d.ts +69 -0
- package/dist/core/component.d.ts.map +1 -0
- package/dist/core/context.d.ts +22 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/errors.d.ts +125 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/event-bus.d.ts +29 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/forms.d.ts +128 -0
- package/dist/core/forms.d.ts.map +1 -0
- package/dist/core/hooks.d.ts +84 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/jsx-runtime.d.ts +45 -0
- package/dist/core/jsx-runtime.d.ts.map +1 -0
- package/dist/core/lifecycle-events.d.ts +71 -0
- package/dist/core/lifecycle-events.d.ts.map +1 -0
- package/dist/core/renderer.d.ts +39 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/router.d.ts +108 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/server-renderer.d.ts +26 -0
- package/dist/core/server-renderer.d.ts.map +1 -0
- package/dist/core/store.d.ts +102 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/types.d.ts +66 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/utils.d.ts +153 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/vdom.d.ts +29 -0
- package/dist/core/vdom.d.ts.map +1 -0
- package/dist/index.d.ts +39 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2592 -508
- package/dist/index.js.map +4 -4
- package/dist/server/auth.d.ts +8 -3
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +173 -112
- package/dist/server/index.js.map +4 -4
- package/dist/server/middleware.d.ts +10 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/package.json +224 -124
- package/templates/basic-app/src/main.jsx +336 -0
- package/templates/complete-app/api/hello.js +0 -0
- package/templates/complete-app/client.js +58 -0
- package/templates/complete-app/lib/frontend-hamroun.js +182 -0
- package/templates/complete-app/package.json +18 -0
- package/templates/complete-app/pages/about.js +119 -0
- package/templates/complete-app/pages/about.jsx +0 -0
- package/templates/complete-app/pages/index.js +157 -0
- package/templates/complete-app/pages/index.jsx +0 -0
- package/templates/complete-app/pages/wasm-demo.js +290 -0
- package/templates/complete-app/pages/wasm-demo.jsx +0 -0
- package/templates/complete-app/public/client.js +89 -0
- package/templates/complete-app/public/index.html +118 -0
- package/templates/complete-app/public/styles.css +76 -0
- package/templates/complete-app/readme.md +188 -0
- package/templates/complete-app/server.js +226 -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/complete-app/wasm/build.bat +0 -0
- package/templates/complete-app/wasm/build.sh +0 -0
- package/templates/complete-app/wasm/example.go +0 -0
- package/templates/fullstack-app/README.md +37 -0
- package/templates/fullstack-app/api/hello.js +11 -0
- package/templates/fullstack-app/api/hello.ts +18 -0
- package/templates/fullstack-app/api/users/[id].ts +73 -0
- package/templates/fullstack-app/api/users/index.ts +32 -0
- package/templates/fullstack-app/build/main.css +874 -0
- package/templates/fullstack-app/build/main.css.map +7 -0
- package/templates/fullstack-app/build/main.js +967 -0
- package/templates/fullstack-app/build/main.js.map +7 -0
- package/templates/fullstack-app/build.ts +210 -0
- package/templates/fullstack-app/index.html +36 -0
- package/templates/fullstack-app/package.json +35 -0
- package/templates/fullstack-app/postcss.config.js +6 -0
- package/templates/fullstack-app/process-tailwind.js +45 -0
- package/templates/fullstack-app/public/_redirects +1 -0
- package/templates/fullstack-app/public/route-handler.js +47 -0
- package/templates/fullstack-app/public/spa-fix.html +17 -0
- package/templates/fullstack-app/public/styles.css +975 -0
- package/templates/fullstack-app/public/tailwind.css +15 -0
- package/templates/fullstack-app/server.js +267 -0
- package/templates/fullstack-app/server.ts +474 -0
- package/templates/fullstack-app/src/README.md +55 -0
- package/templates/fullstack-app/src/client.js +87 -0
- package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
- package/templates/fullstack-app/src/components/ErrorBoundary.tsx +36 -0
- package/templates/fullstack-app/src/components/Layout.tsx +42 -0
- package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
- package/templates/fullstack-app/src/components/UserList.tsx +44 -0
- package/templates/fullstack-app/src/config.ts +42 -0
- package/templates/fullstack-app/src/data/api.ts +206 -0
- package/templates/fullstack-app/src/main.tsx +155 -0
- package/templates/fullstack-app/src/middleware.ts +28 -0
- package/templates/fullstack-app/src/pages/404.tsx +28 -0
- package/templates/fullstack-app/src/pages/[id].tsx +0 -0
- package/templates/fullstack-app/src/pages/_app.tsx +11 -0
- package/templates/fullstack-app/src/pages/_document.tsx +25 -0
- package/templates/fullstack-app/src/pages/_error.tsx +45 -0
- package/templates/fullstack-app/src/pages/about/index.tsx +67 -0
- package/templates/fullstack-app/src/pages/about.tsx +71 -0
- package/templates/fullstack-app/src/pages/api/users/[id].ts +73 -0
- package/templates/fullstack-app/src/pages/api/users/index.ts +43 -0
- package/templates/fullstack-app/src/pages/index.tsx +110 -0
- package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
- package/templates/fullstack-app/src/pages/users.tsx +60 -0
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
- package/templates/fullstack-app/src/router.ts +255 -0
- package/templates/fullstack-app/src/styles.css +5 -0
- package/templates/fullstack-app/tailwind.config.js +11 -0
- package/templates/fullstack-app/tsconfig.json +18 -0
- package/templates/fullstack-app/vite.config.js +87 -0
- package/templates/go/build.bat +36 -0
- package/templates/go/build.sh +43 -0
- package/templates/go/example.go +205 -0
- package/templates/go-wasm-app/README.md +38 -0
- package/templates/go-wasm-app/babel.config.js +21 -0
- package/templates/go-wasm-app/build-client.js +49 -0
- package/templates/go-wasm-app/build-wasm.js +237 -0
- package/templates/go-wasm-app/build.config.js +62 -0
- package/templates/go-wasm-app/build.js +218 -0
- package/templates/go-wasm-app/package.json +32 -0
- package/templates/go-wasm-app/public/index.html +128 -0
- package/templates/go-wasm-app/public/styles.css +197 -0
- package/templates/go-wasm-app/public/wasm/example.wasm +0 -0
- package/templates/go-wasm-app/public/wasm/wasm_exec.js +561 -0
- package/templates/go-wasm-app/public/wasm/wasm_exec_node.js +39 -0
- package/templates/go-wasm-app/server.js +70 -0
- package/templates/go-wasm-app/src/App.jsx +38 -0
- package/templates/go-wasm-app/src/app.js +173 -0
- package/templates/go-wasm-app/src/client.js +57 -0
- package/templates/go-wasm-app/src/components/Footer.jsx +13 -0
- package/templates/go-wasm-app/src/components/Header.jsx +19 -0
- package/templates/go-wasm-app/src/components/WasmDemo.jsx +120 -0
- package/templates/go-wasm-app/src/main.jsx +12 -0
- package/templates/go-wasm-app/src/wasm/example.go +75 -0
- package/templates/go-wasm-app/tsconfig.server.json +18 -0
- package/templates/go-wasm-app/vite.config.js +45 -0
- package/dist/forms/index.d.ts +0 -2
- package/dist/forms/index.d.ts.map +0 -1
- package/dist/forms/types.d.ts +0 -2
- package/dist/forms/types.d.ts.map +0 -1
- package/dist/frontend/components/WasmLoader.d.ts +0 -2
- package/dist/frontend/components/WasmLoader.d.ts.map +0 -1
- package/dist/frontend/hooks/useWasm.d.ts +0 -2
- package/dist/frontend/hooks/useWasm.d.ts.map +0 -1
- package/dist/frontend/index.d.ts +0 -2
- package/dist/frontend/index.d.ts.map +0 -1
- package/dist/router/index.d.ts +0 -2
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/types.d.ts +0 -2
- package/dist/router/types.d.ts.map +0 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/types.d.ts +0 -2
- package/dist/store/types.d.ts.map +0 -1
- package/templates/basic-app/package-lock.json +0 -7173
- package/templates/fullstack-template/package-lock.json +0 -3558
- package/templates/ssr-template/package-lock.json +0 -5380
- package/templates/wasm/package-lock.json +0 -5347
- 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
|
+
}
|
|
File without changes
|
|
@@ -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
|
+
© {new Date().getFullYear()} Frontend Hamroun App. All rights reserved.
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</footer>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|