frontend-hamroun 1.1.47 → 1.1.48

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/README.md CHANGED
@@ -95,12 +95,222 @@ function ThemedButton() {
95
95
  }
96
96
  ```
97
97
 
98
- ### Server-Side Rendering
98
+ ## Server-Side Rendering
99
99
 
100
+ ### Quick Start
100
101
  ```tsx
102
+ // server.ts
101
103
  import { renderToString } from 'frontend-hamroun';
102
104
 
103
- const html = await renderToString(<App />);
105
+ const html = await renderToString(<App />, {
106
+ title: 'My SSR App',
107
+ description: 'A server-rendered application',
108
+ scripts: ['/client.js'],
109
+ initialState: {
110
+ user: { id: 1, name: 'John' }
111
+ }
112
+ });
113
+ ```
114
+
115
+ ### Complete SSR Setup
116
+
117
+ 1. Server Setup:
118
+ ```typescript
119
+ // server.ts
120
+ import express from 'express';
121
+ import { renderToString } from 'frontend-hamroun';
122
+
123
+ const app = express();
124
+
125
+ app.get('*', async (req, res) => {
126
+ try {
127
+ const html = await renderToString(<App />, {
128
+ title: 'My App',
129
+ description: 'Server-rendered app',
130
+ scripts: ['/client.js'],
131
+ styles: ['/styles.css'],
132
+ meta: {
133
+ 'og:title': 'My App',
134
+ 'theme-color': '#ffffff'
135
+ },
136
+ initialState: {
137
+ url: req.url,
138
+ user: req.user
139
+ },
140
+ bodyAttrs: { class: 'app-root' },
141
+ htmlAttrs: { lang: 'en' }
142
+ });
143
+
144
+ res.send(html);
145
+ } catch (error) {
146
+ res.status(500).send('Server Error');
147
+ }
148
+ });
149
+ ```
150
+
151
+ 2. Client Hydration:
152
+ ```typescript
153
+ // client.tsx
154
+ import { hydrate } from 'frontend-hamroun';
155
+
156
+ // Type-safe state access
157
+ declare global {
158
+ interface Window {
159
+ __INITIAL_STATE__: {
160
+ url: string;
161
+ user?: { id: number; name: string };
162
+ }
163
+ }
164
+ }
165
+
166
+ const { url, user } = window.__INITIAL_STATE__;
167
+
168
+ hydrate(<App url={url} user={user} />, document.getElementById('root')!);
169
+ ```
170
+
171
+ 3. Template Customization:
172
+ ```typescript
173
+ // Custom template options
174
+ interface TemplateOptions {
175
+ title: string;
176
+ description?: string;
177
+ scripts?: string[];
178
+ styles?: string[];
179
+ meta?: Record<string, string>;
180
+ initialState?: any;
181
+ bodyAttrs?: Record<string, string>;
182
+ htmlAttrs?: Record<string, string>;
183
+ }
184
+ ```
185
+
186
+ ### SSR Features
187
+
188
+ - ✨ Full HTML document generation
189
+ - 🔄 Automatic state hydration
190
+ - 🎨 Customizable templates
191
+ - 📱 Meta tags for SEO
192
+ - 🛡️ XSS protection
193
+ - 🚀 Streaming support
194
+ - 📦 Asset optimization
195
+ - 🔍 Error handling
196
+
197
+ ### Best Practices
198
+
199
+ 1. State Management:
200
+ ```typescript
201
+ // Shared types between server and client
202
+ interface AppState {
203
+ url: string;
204
+ user?: UserData;
205
+ settings: AppSettings;
206
+ }
207
+
208
+ // Server
209
+ const state: AppState = {
210
+ url: req.url,
211
+ user: await getUser(req),
212
+ settings: await getSettings()
213
+ };
214
+
215
+ // Client
216
+ const { url, user, settings } = window.__INITIAL_STATE__ as AppState;
217
+ ```
218
+
219
+ 2. Error Handling:
220
+ ```typescript
221
+ // Server-side error boundary
222
+ try {
223
+ const html = await renderToString(<App />, options);
224
+ res.send(html);
225
+ } catch (error) {
226
+ const errorHtml = await renderToString(
227
+ <ErrorPage error={error} />,
228
+ { title: 'Error' }
229
+ );
230
+ res.status(500).send(errorHtml);
231
+ }
232
+ ```
233
+
234
+ 3. Performance Optimization:
235
+ ```typescript
236
+ // Caching
237
+ const cached = await cache.get(cacheKey);
238
+ if (cached) return cached;
239
+
240
+ const html = await renderToString(<App />, {
241
+ ...options,
242
+ scripts: [
243
+ { src: '/client.js', async: true, defer: true }
244
+ ]
245
+ });
246
+
247
+ await cache.set(cacheKey, html, '1h');
248
+ ```
249
+
250
+ ### Basic SSR Setup
251
+
252
+ ```typescript
253
+ // Server-side rendering with customization
254
+ const html = await renderToString(<App />, {
255
+ title: 'My App',
256
+ description: 'Server-rendered application',
257
+ scripts: ['/assets/client.js'],
258
+ styles: ['/assets/styles.css'],
259
+ meta: {
260
+ 'theme-color': '#ffffff',
261
+ 'og:title': 'My App',
262
+ 'og:description': 'My awesome app'
263
+ },
264
+ initialState: {
265
+ user: { name: 'John' }
266
+ },
267
+ bodyAttrs: { class: 'dark-mode' },
268
+ htmlAttrs: { lang: 'en', dir: 'ltr' }
269
+ });
270
+ ```
271
+
272
+ ### Express Server Integration
273
+
274
+ ```typescript
275
+ import express from 'express';
276
+ import { renderToString } from 'frontend-hamroun';
277
+
278
+ const app = express();
279
+
280
+ app.get('*', async (req, res) => {
281
+ const initialState = {
282
+ url: req.url,
283
+ user: req.user // From your auth middleware
284
+ };
285
+
286
+ const html = await renderToString(<App />, {
287
+ title: 'My App',
288
+ scripts: ['/client.js'],
289
+ initialState
290
+ });
291
+
292
+ res.send(html);
293
+ });
294
+ ```
295
+
296
+ ### Client-Side Hydration
297
+
298
+ ```typescript
299
+ import { hydrate } from 'frontend-hamroun';
300
+
301
+ // Access the server-provided state
302
+ declare global {
303
+ interface Window {
304
+ __INITIAL_STATE__: any;
305
+ }
306
+ }
307
+
308
+ const initialState = window.__INITIAL_STATE__;
309
+
310
+ hydrate(
311
+ <App initialState={initialState} />,
312
+ document.getElementById('root')!
313
+ );
104
314
  ```
105
315
 
106
316
  ## CLI Tools
@@ -140,6 +350,49 @@ npx create-frontend-app my-app
140
350
  - Error boundaries
141
351
  - Performance monitoring
142
352
 
353
+ ## Advanced Features
354
+
355
+ ### Template Customization
356
+ ```typescript
357
+ interface TemplateOptions {
358
+ title: string;
359
+ description?: string;
360
+ scripts?: string[];
361
+ styles?: string[];
362
+ meta?: Record<string, string>;
363
+ initialState?: any;
364
+ bodyAttrs?: Record<string, string>;
365
+ htmlAttrs?: Record<string, string>;
366
+ }
367
+
368
+ // Usage with custom template
369
+ const html = await renderToString(<App />, {
370
+ title: 'Custom Template',
371
+ styles: ['/styles.css'],
372
+ bodyAttrs: { 'data-theme': 'dark' }
373
+ });
374
+ ```
375
+
376
+ ### Error Handling
377
+ ```typescript
378
+ // Error boundary component
379
+ function ErrorBoundary({ children }) {
380
+ const [error, resetError] = useErrorBoundary();
381
+
382
+ if (error) {
383
+ return (
384
+ <div role="alert">
385
+ <h2>Something went wrong</h2>
386
+ <pre>{error.message}</pre>
387
+ <button onClick={resetError}>Try again</button>
388
+ </div>
389
+ );
390
+ }
391
+
392
+ return children;
393
+ }
394
+ ```
395
+
143
396
  ## Contributing
144
397
 
145
398
  1. Fork the repository
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-hamroun",
3
- "version": "1.1.47",
3
+ "version": "1.1.48",
4
4
  "description": "A lightweight frontend framework with hooks and virtual DOM",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,26 +1,36 @@
1
- import { useState } from 'frontend-hamroun';
1
+ import { useState, useEffect } from 'frontend-hamroun';
2
+
3
+ interface AppProps {
4
+ initialUrl?: string;
5
+ initialSettings?: {
6
+ theme: string;
7
+ };
8
+ user?: any;
9
+ }
10
+
11
+ export function App({
12
+ initialUrl = '/',
13
+ initialSettings = { theme: 'light' },
14
+ user = null
15
+ }: AppProps) {
16
+ const [theme, setTheme] = useState(initialSettings.theme);
17
+
18
+ useEffect(() => {
19
+ document.body.dataset.theme = theme;
20
+ }, [theme]);
2
21
 
3
- export function App() {
4
- // Initialize with a default state that works on both server and client
5
- const [count, setCount] = useState(0);
6
-
7
22
  return (
8
- <div>
9
- <h1>Server-Side Rendered App</h1>
10
- <div>
11
- <button
12
- onClick={() => setCount(count - 1)}
13
- data-action="decrement"
14
- >-</button>
15
- <span style={{ margin: '0 10px' }}>{count}</span>
16
- <button
17
- onClick={() => setCount(count + 1)}
18
- data-action="increment"
19
- >+</button>
20
- </div>
21
- <script dangerouslySetInnerHTML={{
22
- __html: `window.__INITIAL_STATE__ = ${JSON.stringify({ count: 0 })};`
23
- }} />
23
+ <div className="app">
24
+ <header>
25
+ <h1>My SSR App</h1>
26
+ <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
27
+ Toggle Theme
28
+ </button>
29
+ </header>
30
+ <main>
31
+ <p>Current URL: {initialUrl}</p>
32
+ {user && <p>Welcome, {user.name}!</p>}
33
+ </main>
24
34
  </div>
25
35
  );
26
36
  }
@@ -1,11 +1,29 @@
1
1
  import { hydrate } from 'frontend-hamroun';
2
2
  import { App } from './App';
3
3
 
4
+ // Type-safe initial state access
5
+ declare global {
6
+ interface Window {
7
+ __INITIAL_STATE__: {
8
+ url: string;
9
+ user: any;
10
+ settings: {
11
+ theme: string;
12
+ };
13
+ };
14
+ }
15
+ }
4
16
 
17
+ // Get state from server
18
+ const initialState = window.__INITIAL_STATE__;
5
19
 
6
20
  document.addEventListener('DOMContentLoaded', () => {
7
21
  hydrate(
8
- <App />,
22
+ <App
23
+ initialUrl={initialState.url}
24
+ initialSettings={initialState.settings}
25
+ user={initialState.user}
26
+ />,
9
27
  document.getElementById('root')!
10
28
  );
11
29
  });
@@ -1,43 +1,38 @@
1
1
  import express from 'express';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { renderToString, jsx } from 'frontend-hamroun';
5
- import { App } from './App.js';
6
- import fs from 'fs';
7
- import { title } from 'process';
4
+ import { renderToString } from 'frontend-hamroun';
5
+ import { App } from './App';
8
6
 
9
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
8
  const app = express();
11
9
  const port = 3000;
12
10
 
13
- // Find the client entry file
14
- function getClientEntry(): string {
15
- const assetsDir = path.join(__dirname, './');
16
- const files = fs.readdirSync(assetsDir);
17
- const entry = files.find(file => file.startsWith("client") && file.endsWith('.js'));
18
- if (!entry) {
19
- throw new Error('Client entry file not found');
20
- }
21
- return entry;
22
- }
23
-
24
- // Static file serving
25
- app.use('/assets', express.static(path.join(__dirname, './')));
26
- app.use(express.static(path.join(__dirname, 'dist')));
11
+ // Static files
12
+ app.use('/assets', express.static(path.join(__dirname, './assets')));
27
13
 
28
- // Main route handler
29
- app.get('/', async (_req: any, res: { send: (arg0: string) => void; status: (arg0: number) => { (): any; new(): any; send: { (arg0: string): void; new(): any; }; }; }) => {
14
+ // All routes handler
15
+ app.get('*', async (req, res) => {
30
16
  try {
31
- const clientEntry = getClientEntry();
32
- const html = await renderToString({
33
- element: jsx(App, null),
34
- title: 'SSR App',
35
- scripts: [`/assets/${clientEntry}`],
36
- initialState: {
37
- // Add any initial state here
38
- },
17
+ // Get initial state based on route
18
+ const initialState = {
19
+ url: req.url,
20
+ user: null, // Add your user data here
21
+ settings: { theme: 'light' }
22
+ };
23
+
24
+ const html = await renderToString(<App />, {
25
+ title: 'My SSR App',
26
+ description: 'Server-side rendered application',
27
+ scripts: ['/assets/client.js'],
28
+ styles: ['/assets/styles.css'],
29
+ initialState,
39
30
  meta: {
40
- 'viewport': 'width=device-width, initial-scale=1.0',
31
+ 'theme-color': '#ffffff',
32
+ 'og:title': 'My SSR App'
33
+ },
34
+ bodyAttrs: {
35
+ 'data-theme': initialState.settings.theme
41
36
  }
42
37
  });
43
38
 
@@ -49,5 +44,5 @@ app.get('/', async (_req: any, res: { send: (arg0: string) => void; status: (arg
49
44
  });
50
45
 
51
46
  app.listen(port, () => {
52
- console.log(`Server running at http://localhost:${port}`);
47
+ console.log(`✓ Server running at http://localhost:${port}`);
53
48
  });