@ressjs/vite-router 0.4.1 → 0.5.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ressjs/vite-router
2
2
 
3
- Advanced Express router with platform detection and Server-Side Rendering (SSR) for RESS.js applications.
3
+ Advanced Express router with **dynamic routes**, platform detection, and Server-Side Rendering (SSR) for RESS.js applications.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,16 +10,50 @@ npm install @ressjs/vite-router
10
10
 
11
11
  ## Features
12
12
 
13
- - **Advanced Routing**: Auto-discovers pages with platform-specific entry points
14
- - **Platform Detection**: Automatic device, OS, and WebView detection
15
- - **SSR Support**: Complete server-side rendering with proper hydration
16
- - **Asset Management**: Smart CSS/JS bundle resolution based on platform
17
- - **Development Mode**: Hot reload with fast refresh and dev server
18
- - **Production Ready**: Optimized builds with manifest-based asset loading
19
- - **TypeScript**: Full TypeScript support with comprehensive type definitions
13
+ - **🎯 Dynamic Routes**: Next.js-style routing with `[param]` syntax
14
+ - **🎨 Platform Detection**: Automatic device, OS, and WebView detection
15
+ - **⚡ SSR Support**: Complete server-side rendering with proper hydration
16
+ - **📱 Smart Assets**: Platform-specific CSS/JS bundle resolution
17
+ - **🔧 Middleware System**: File-based middleware with execution order control
18
+ - **🔄 Development Mode**: Hot reload with fast refresh and dev server
19
+ - **🚀 Production Ready**: Optimized builds with manifest-based asset loading
20
+ - **📝 TypeScript**: Full TypeScript support with comprehensive type definitions
20
21
 
21
22
  ## Quick Start
22
23
 
24
+ ### Vite Configuration
25
+
26
+ Create a `vite.config.ts` file in your project root:
27
+
28
+ ```typescript
29
+ import { defineConfig } from 'vite'
30
+ import react from '@vitejs/plugin-react'
31
+ import { getClientEntriesInput, getViteSSRInput } from '@ressjs/vite-router'
32
+
33
+ const isSSR = !!process.env.VITE_SSR_BUILD
34
+
35
+ export default defineConfig(async () => ({
36
+ plugins: [react()],
37
+ server: {
38
+ watch: {
39
+ ignored: ['**/dist/.entries/**'] // Ignore generated entries
40
+ }
41
+ },
42
+ build: {
43
+ manifest: true,
44
+ outDir: isSSR ? 'dist/server' : 'dist/client',
45
+ rollupOptions: {
46
+ input: isSSR
47
+ ? getViteSSRInput() // SSR entry point
48
+ : await getClientEntriesInput(), // Auto-generated client entries
49
+ },
50
+ ssr: isSSR,
51
+ },
52
+ }))
53
+ ```
54
+
55
+ **Important**: The router automatically generates entry files for each page/platform combination in `dist/.entries/`. These are used by Vite for building client bundles.
56
+
23
57
  ### Basic Setup
24
58
 
25
59
  ```typescript
@@ -36,55 +70,368 @@ app.use('/', router)
36
70
  app.listen(3000, () => console.log('Server running on port 3000'))
37
71
  ```
38
72
 
39
- ### Development Server
73
+ ### Development Server Setup
74
+
75
+ Create a `server.js` file for development:
76
+
77
+ ```typescript
78
+ import { createServer } from 'vite'
79
+ import { createViteRouter } from '@ressjs/vite-router'
80
+ import express from 'express'
81
+
82
+ async function createDevServer() {
83
+ const app = express()
84
+
85
+ // Create Vite server in middleware mode
86
+ const vite = await createServer({
87
+ server: { middlewareMode: true },
88
+ appType: 'custom'
89
+ })
90
+
91
+ // Use vite's connect instance as middleware
92
+ app.use(vite.ssrLoadModule)
93
+
94
+ // Create router with Vite instance
95
+ const router = createViteRouter({
96
+ vite,
97
+ enablePlatformDetection: true,
98
+ isProduction: false
99
+ })
100
+
101
+ app.use('/', router)
102
+ app.listen(3000, () => console.log('Dev server running on port 3000'))
103
+ }
104
+
105
+ createDevServer()
106
+ ```
107
+
108
+ ### Package.json Scripts
109
+
110
+ Add these scripts to your `package.json`:
111
+
112
+ ```json
113
+ {
114
+ "scripts": {
115
+ "dev": "node server.js",
116
+ "build": "npm run build:client && npm run build:server",
117
+ "build:client": "vite build",
118
+ "build:server": "VITE_SSR_BUILD=true vite build",
119
+ "preview": "NODE_ENV=production node server.js",
120
+ "type-check": "tsc --noEmit"
121
+ },
122
+ "dependencies": {
123
+ "@ressjs/vite-router": "^0.5.0",
124
+ "express": "^4.18.0",
125
+ "react": "^18.0.0",
126
+ "react-dom": "^18.0.0",
127
+ "vite": "^5.0.0"
128
+ },
129
+ "devDependencies": {
130
+ "@types/express": "^4.17.0",
131
+ "@types/react": "^18.0.0",
132
+ "@types/react-dom": "^18.0.0",
133
+ "@vitejs/plugin-react": "^4.0.0",
134
+ "typescript": "^5.0.0"
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### TypeScript Backend Integration
140
+
141
+ For full-stack TypeScript projects with backend APIs, use this enhanced configuration:
142
+
143
+ ```json
144
+ {
145
+ "name": "my-fullstack-app",
146
+ "type": "module",
147
+ "scripts": {
148
+ "dev": "PORT=3000 vite-node server.ts",
149
+ "build": "npm run build:client && npm run build:server",
150
+ "build:client": "vite build --outDir dist/client",
151
+ "build:server": "cross-env VITE_SSR_BUILD=1 vite build --ssr --outDir dist/server",
152
+ "preview": "cross-env NODE_ENV=production PORT=3000 tsx server.ts",
153
+ "start": "npm run build && npm run preview"
154
+ },
155
+ "dependencies": {
156
+ "@ressjs/vite-router": "^0.5.0",
157
+ "cross-env": "^7.0.3",
158
+ "express": "^4.18.0",
159
+ "react": "^18.0.0",
160
+ "react-dom": "^18.0.0",
161
+ "tsx": "^4.7.1",
162
+ "typescript": "^5.0.0",
163
+ "vite": "^5.0.0"
164
+ },
165
+ "devDependencies": {
166
+ "@types/express": "^4.17.0",
167
+ "@types/node": "^22.0.0",
168
+ "@types/react": "^18.0.0",
169
+ "@types/react-dom": "^18.0.0",
170
+ "@vitejs/plugin-react": "^4.0.0",
171
+ "vite-node": "^3.2.0"
172
+ }
173
+ }
174
+ ```
175
+
176
+ **Key differences for TypeScript backends**:
177
+ - **`vite-node server.ts`**: Direct TypeScript execution in development
178
+ - **`tsx server.ts`**: Fast TypeScript runner for production
179
+ - **`type: "module"`**: Enable ES modules in Node.js
180
+ - **`cross-env`**: Cross-platform environment variables
181
+
182
+ **When to use this setup**:
183
+ - Building APIs alongside your frontend
184
+ - Need database connections, authentication, file uploads
185
+ - Want full TypeScript integration across the stack
186
+ - Require additional Express middleware and routes
187
+
188
+ **Key Points**:
189
+ - **`VITE_SSR_BUILD=true`**: Environment variable that tells Vite config to build for SSR
190
+ - **Separate builds**: Client and server are built separately with different entry points
191
+ - **Auto-generated entries**: `getClientEntriesInput()` discovers all page/platform combinations
192
+
193
+ ### Auto Server (WIP)
40
194
 
41
195
  ```typescript
42
196
  import { createAutoViteServer } from '@ressjs/vite-router'
43
197
 
44
- // Automatically configures Vite dev server with SSR
45
- const server = await createAutoViteServer({
198
+ // Automatically configures Vite dev server with SSR (Work In Progress)
199
+ const { start } = await createAutoViteServer({
46
200
  port: 3000,
47
201
  enablePlatformDetection: true
48
202
  })
203
+
204
+ start()
205
+ ```
206
+
207
+ ## 🎯 Dynamic Routes
208
+
209
+ Create pages with parameters using Next.js-style bracket syntax:
210
+
211
+ ### File Structure
212
+ ```
213
+ app/pages/
214
+ ├── index.tsx # / route
215
+ ├── about.tsx # /about route
216
+ ├── users/
217
+ │ ├── index.tsx # /users route
218
+ │ └── [id].tsx # /users/:id route
219
+ ├── products/
220
+ │ ├── [category].tsx # /products/:category route
221
+ │ └── [category]/
222
+ │ └── [id].tsx # /products/:category/:id route
223
+ └── blog/
224
+ └── [...slug].tsx # /blog/* route (coming soon)
49
225
  ```
50
226
 
51
- ## Platform Detection
227
+ ### Route Examples
52
228
 
53
- The router automatically detects:
229
+ | File | Route | Express Route | URL Example |
230
+ |------|-------|---------------|-------------|
231
+ | `users/[id].tsx` | `/users/[id]` | `/users/:id` | `/users/123` |
232
+ | `products/[category]/[id].tsx` | `/products/[category]/[id]` | `/products/:category/:id` | `/products/phones/iphone` |
233
+ | `photocard/[photoId].tsx` | `/photocard/[photoId]` | `/photocard/:photoId` | `/photocard/abc123xyz` |
234
+
235
+ ### Accessing Parameters
236
+
237
+ ```typescript
238
+ // app/pages/users/[id].tsx
239
+ export default function UserPage({ id, user }) {
240
+ return (
241
+ <div>
242
+ <h1>User {id}</h1>
243
+ <p>Name: {user.name}</p>
244
+ </div>
245
+ )
246
+ }
54
247
 
55
- - **Device Type**: mobile, tablet, desktop
56
- - **Operating System**: iOS, Android, Windows, macOS, Linux
57
- - **Environment**: webview, browser
58
- - **Category**: webview.android, webview.ios, mobile, etc.
248
+ export async function getServerSideProps(req, res) {
249
+ const { id } = req.params // Access route parameters
250
+
251
+ // Fetch user data
252
+ const user = await fetchUser(id)
253
+
254
+ return {
255
+ props: { id, user }
256
+ }
257
+ }
258
+ ```
59
259
 
60
- ### Platform-Specific Assets
260
+ ### Dynamic Route Styles
61
261
 
62
- Based on detection, the router serves appropriate assets:
262
+ Dynamic routes support platform-specific styles:
63
263
 
64
264
  ```
65
- app/pages/home/index.tsx # Base page
66
- app/pages/home/index.scss # Base styles
67
- app/pages/home/index.mobile.scss # Mobile-specific styles
68
- app/pages/home/index.webview.android.scss # Android WebView styles
265
+ app/pages/products/[id].tsx # Product page component
266
+ app/pages/products/[id].scss # Desktop styles
267
+ app/pages/products/[id].mobile.scss # Mobile styles
268
+ app/pages/products/[id].webview.scss # WebView styles
69
269
  ```
70
270
 
71
- Generated entry-clients:
271
+ ## 🔧 Middleware System
272
+
273
+ **Important**: Dynamic routes `[param].tsx` do **NOT** support dedicated middleware files. Use page-level or directory-level middleware instead.
274
+
275
+ ### Middleware Execution Order
276
+
72
277
  ```
73
- dist/.entries/home__index__entry-client.tsx # Base
74
- dist/.entries/home__index__entry-client.mobile.tsx # Mobile
75
- dist/.entries/home__index__entry-client.webview.android.tsx # Android WebView
278
+ app/pages/
279
+ ├── middlewares.ts # --> Executes 1st (global)
280
+ ├── products/
281
+ │ ├── middlewares.ts # --> Executes 2nd (directory-level)
282
+ │ ├── index.tsx
283
+ │ ├── index.middlewares.ts # --> Executes 3rd (page-specific)
284
+ │ └── [id].tsx # --> No middleware support for dynamic routes
285
+ ├── users/
286
+ │ ├── [id].tsx # --> Uses directory and global middleware only
287
+ │ └── middlewares.ts # --> Executes for all /users/* routes
288
+ └── checkout/
289
+ ├── middlewares.ts # --> Executes 2nd for /checkout/*
290
+ ├── step1.tsx
291
+ ├── step1.middlewares.ts # --> Executes 3rd for /checkout/step1
292
+ ├── step2.tsx
293
+ └── step2.middlewares.ts # --> Executes 3rd for /checkout/step2
76
294
  ```
77
295
 
78
- ## API Reference
296
+ ### Middleware Types
79
297
 
80
- ### createViteRouter(options)
298
+ #### 1. Global Middleware
299
+ ```typescript
300
+ // app/pages/middlewares.ts --> Executes 1st for ALL routes
301
+ export default function globalMiddleware(req, res, next) {
302
+ console.log('Global middleware for:', req.url)
303
+ req.startTime = Date.now()
304
+ next()
305
+ }
306
+ ```
81
307
 
82
- Creates an Express router with SSR capabilities.
308
+ #### 2. Directory Middleware
309
+ ```typescript
310
+ // app/pages/products/middlewares.ts --> Executes 2nd for /products/*
311
+ export default function productsMiddleware(req, res, next) {
312
+ console.log('Products middleware for:', req.url)
313
+ req.section = 'products'
314
+ next()
315
+ }
316
+ ```
317
+
318
+ #### 3. Page-Specific Middleware
319
+ ```typescript
320
+ // app/pages/products/index.middlewares.ts --> Executes 3rd for /products only
321
+ export default function productListMiddleware(req, res, next) {
322
+ console.log('Product list middleware')
323
+ req.page = 'product-list'
324
+ next()
325
+ }
326
+ ```
327
+
328
+ #### ❌ Not Supported for Dynamic Routes
329
+ ```typescript
330
+ // ❌ app/pages/products/[id].middlewares.ts - NOT supported
331
+ // Dynamic routes cannot have dedicated middleware files
332
+ ```
333
+
334
+ ### Complete Execution Flow Example
335
+
336
+ For URL `/products/123`:
337
+
338
+ 1. **Global**: `app/pages/middlewares.ts` (if exists)
339
+ 2. **Directory**: `app/pages/products/middlewares.ts` (if exists)
340
+ 3. **Page**: Built-in `getServerSideProps` from `[id].tsx`
341
+ 4. **Render**: Component render and HTML generation
342
+
343
+ ## 📱 Platform Detection
344
+
345
+ The router automatically detects and serves platform-specific assets:
346
+
347
+ ### Detection Capabilities
348
+
349
+ - **Device Type**: `phone`, `tablet`, `desktop`
350
+ - **Operating System**: `ios`, `android`, `windows`, `macos`, `linux`
351
+ - **Environment**: `webview`, `web`, `native`
352
+ - **Category**: `webview.android`, `webview.ios`, `mobile`, etc.
353
+
354
+ ### Platform Headers (React Native)
355
+
356
+ React Native clients should send these headers:
357
+
358
+ ```javascript
359
+ {
360
+ 'x-ressjs-platform': 'webview', // Required
361
+ 'x-ressjs-os': 'ios', // Required
362
+ 'x-ressjs-device': 'phone', // Required
363
+ 'x-ressjs-version': Platform.Version, // Optional
364
+ 'x-ressjs-app-version': '1.0.0' // Optional
365
+ }
366
+ ```
367
+
368
+ ### Asset Resolution Priority
369
+
370
+ For a page like `app/pages/home/index.tsx`:
371
+
372
+ 1. **Device + OS Specific**: `index.phone.ios.scss`
373
+ 2. **Device Specific**: `index.phone.scss`
374
+ 3. **Category Specific**: `index.mobile.scss`
375
+ 4. **WebView Specific**: `index.webview.scss`
376
+ 5. **Base Fallback**: `index.scss`
377
+
378
+ ### Platform CSS Examples
379
+
380
+ ```
381
+ app/pages/home/
382
+ ├── index.tsx # React component
383
+ ├── index.scss # Desktop/base styles
384
+ ├── index.mobile.scss # Phone + tablet
385
+ ├── index.phone.scss # Phone only
386
+ ├── index.tablet.scss # Tablet only
387
+ ├── index.webview.scss # All WebView
388
+ ├── index.webview.ios.scss # iOS WebView
389
+ ├── index.webview.android.scss # Android WebView
390
+ └── index.desktop.linux.scss # Linux desktop
391
+ ```
392
+
393
+ ## 🎨 getServerSideProps
394
+
395
+ Every page can export a `getServerSideProps` function for server-side data fetching:
396
+
397
+ ```typescript
398
+ export async function getServerSideProps(req, res) {
399
+ // Access route parameters (for dynamic routes)
400
+ const { id, category } = req.params
401
+
402
+ // Access query parameters
403
+ const { search, filter } = req.query
404
+
405
+ // Access request headers
406
+ const userAgent = req.headers['user-agent']
407
+
408
+ // Return props for the component
409
+ return {
410
+ props: {
411
+ id,
412
+ data: await fetchData(id),
413
+ timestamp: new Date().toISOString()
414
+ },
415
+ // Optional: HTML modifications
416
+ title: `Product ${id}`,
417
+ html: {
418
+ head: {
419
+ title: `Custom Title`,
420
+ extraTags: ['<meta name="description" content="...">']
421
+ }
422
+ }
423
+ }
424
+ }
425
+ ```
426
+
427
+ ## 🚀 API Reference
428
+
429
+ ### createViteRouter(options)
83
430
 
84
431
  ```typescript
85
432
  interface ViteRouterOptions {
86
433
  enablePlatformDetection?: boolean // Enable platform detection (default: true)
87
- vite?: any // Vite dev server instance
434
+ vite?: any // Vite dev server instance
88
435
  manifest?: any // Production manifest
89
436
  templateHtml?: string // HTML template
90
437
  isProduction?: boolean // Production mode
@@ -96,61 +443,89 @@ interface ViteRouterOptions {
96
443
  }
97
444
  ```
98
445
 
99
- ### generateHTML(template, rendered, page, manifest, platform, isProduction, platformInfo)
100
-
101
- Generates complete HTML with proper asset injection and hydration.
446
+ ### createAutoViteServer(options)
102
447
 
103
448
  ```typescript
104
- export async function generateHTML(
105
- template: string,
106
- rendered: any,
107
- page: any,
108
- manifest: any,
109
- platform: string,
110
- isProduction: boolean,
111
- platformInfo: any = null
112
- ): Promise<string>
449
+ interface AutoViteServerOptions {
450
+ port?: number // Server port (default: 5173)
451
+ base?: string // Base path (default: '/')
452
+ enablePlatformDetection?: boolean // Enable platform detection (default: true)
453
+ }
113
454
  ```
114
455
 
115
456
  ### Utility Functions
116
457
 
117
458
  ```typescript
118
- // Get all discovered pages
119
- import { getPages, listDetectedPages } from '@ressjs/vite-router'
120
-
121
- const pages = await getPages()
122
- const pageList = listDetectedPages()
459
+ import {
460
+ getPages,
461
+ listDetectedPages,
462
+ convertRouteToExpress
463
+ } from '@ressjs/vite-router'
464
+
465
+ // Get all discovered pages with route info
466
+ const pages = getPages()
467
+ // [{ route: '/users/[id]', expressRoute: '/users/:id', file: '...', abs: '...' }]
468
+
469
+ // Convert Next.js route to Express route
470
+ const expressRoute = convertRouteToExpress('/users/[id]') // '/users/:id'
123
471
  ```
124
472
 
125
- ## File Structure
473
+ ## 🔧 Production Build
126
474
 
127
- The router expects the following structure:
475
+ ### Build Process
128
476
 
129
- ```
130
- app/
131
- pages/
132
- index.tsx # Home page
133
- about/
134
- index.tsx # About page
135
- index.mobile.scss # Mobile styles
136
- products/
137
- [id].tsx # Dynamic route
138
- [id].webview.ios.scss # iOS WebView styles
477
+ ```bash
478
+ # 1. Build client assets
479
+ vite build --outDir dist/client
480
+
481
+ # 2. Build server bundle
482
+ vite build --ssr --outDir dist/server
483
+
484
+ # 3. Start production server
485
+ NODE_ENV=production node server.js
139
486
  ```
140
487
 
141
- ## Development
488
+ ### Example Production Server
142
489
 
143
- ```bash
144
- # Build the package
145
- npm run build
490
+ ```typescript
491
+ import express from 'express'
492
+ import { createViteRouter } from '@ressjs/vite-router'
493
+
494
+ const app = express()
146
495
 
147
- # Run tests
148
- npm test
496
+ // Serve static assets
497
+ app.use('/assets', express.static('dist/client/assets'))
149
498
 
150
- # Watch mode
151
- npm run dev
499
+ // Create router with production settings
500
+ const router = createViteRouter({
501
+ isProduction: true,
502
+ enablePlatformDetection: true
503
+ })
504
+
505
+ app.use('/', router)
506
+ app.listen(3000)
152
507
  ```
153
508
 
154
- ## License
509
+ ## 🐛 Troubleshooting
510
+
511
+ ### Dynamic Route Not Working
512
+
513
+ 1. **Check file naming**: Use `[param].tsx`, not `{param}.tsx`
514
+ 2. **Verify parameters**: Access via `req.params.param` in `getServerSideProps`
515
+ 3. **Build order**: Run `npm run build` after adding new dynamic routes
516
+
517
+ ### Middleware Not Executing
518
+
519
+ 1. **File naming**: Use `middlewares.ts` (plural) or `page.middlewares.ts`
520
+ 2. **Dynamic routes**: Don't create `[param].middlewares.ts` - not supported
521
+ 3. **Export**: Use `export default function` format
522
+
523
+ ### Platform Detection Issues
524
+
525
+ 1. **Headers**: Ensure React Native sends required `x-ressjs-*` headers
526
+ 2. **CSS files**: Create platform-specific CSS files if needed
527
+ 3. **Case sensitivity**: Use lowercase for OS names (`ios`, `android`)
528
+
529
+ ## 📄 License
155
530
 
156
531
  MIT
@@ -7,6 +7,7 @@ export interface HTMLGenerationContext {
7
7
  platform: string;
8
8
  isProduction: boolean;
9
9
  platformInfo: PlatformInfo | null;
10
+ serverSideResult?: any;
10
11
  }
11
12
  export declare function generateClientScript(pageFile: string, platformInfo: PlatformInfo | null, manifest: any, isProduction: boolean): Promise<string | null>;
12
13
  export declare function generateClientCSS(pageFile: string, platformInfo: PlatformInfo | null, manifest: any, isProduction: boolean): string;
@@ -8,7 +8,12 @@ exports.generateClientCSS = generateClientCSS;
8
8
  exports.generateCompleteHTML = generateCompleteHTML;
9
9
  const promises_1 = __importDefault(require("node:fs/promises"));
10
10
  async function generateClientScript(pageFile, platformInfo, manifest, isProduction) {
11
- const rel = pageFile.replace(/^app\/pages\//, '').replace(/\.tsx$|\.jsx$/, '').replace(/\//g, '__');
11
+ // Sanitizar nombre: convertir [id] a _id_ para nombres de archivo (igual que en pages.ts)
12
+ const rel = pageFile
13
+ .replace(/^app\/pages\//, '')
14
+ .replace(/\.tsx$|\.jsx$/, '')
15
+ .replace(/\//g, '__')
16
+ .replace(/\[([^\]]+)\]/g, '_$1_');
12
17
  let entryKey = `${rel}__entry-client.tsx`;
13
18
  if (platformInfo && platformInfo.device !== 'desktop') {
14
19
  // Priority order (from highest to lowest):
@@ -59,7 +64,12 @@ function generateClientCSS(pageFile, platformInfo, manifest, isProduction) {
59
64
  if (!isProduction || !pageFile) {
60
65
  return '';
61
66
  }
62
- const rel = pageFile.replace(/^app\/pages\//, '').replace(/\.tsx$|\.jsx$/, '').replace(/\//g, '__');
67
+ // Sanitizar nombre: convertir [id] a _id_ para nombres de archivo (igual que en pages.ts)
68
+ const rel = pageFile
69
+ .replace(/^app\/pages\//, '')
70
+ .replace(/\.tsx$|\.jsx$/, '')
71
+ .replace(/\//g, '__')
72
+ .replace(/\[([^\]]+)\]/g, '_$1_');
63
73
  let entryKey = `dist/.entries/${rel}__entry-client.tsx`;
64
74
  if (platformInfo && platformInfo.device !== 'desktop') {
65
75
  // Priority order (from highest to lowest):
@@ -90,12 +100,40 @@ function generateClientCSS(pageFile, platformInfo, manifest, isProduction) {
90
100
  return '';
91
101
  }
92
102
  async function generateCompleteHTML(context) {
93
- const { template, rendered, page, manifest, isProduction, platformInfo } = context;
103
+ const { template, rendered, page, manifest, isProduction, platformInfo, serverSideResult } = context;
94
104
  const scriptSrc = await generateClientScript(page.file, platformInfo, manifest, isProduction);
95
105
  const scriptTag = scriptSrc ? `<script type="module" src="${scriptSrc}"></script>` : '';
96
106
  const cssLinks = page?.file ? generateClientCSS(page.file, platformInfo, manifest, isProduction) : '';
97
- const html = template
98
- .replace('<!--app-head-->', cssLinks)
107
+ // Construir contenido HEAD extra y atributos desde serverSideResult
108
+ let headContent = cssLinks;
109
+ let htmlAttributes = '';
110
+ let bodyAttributes = '';
111
+ let pageTitle = '';
112
+ if (serverSideResult?.html) {
113
+ if (serverSideResult.html.head?.attributes) {
114
+ htmlAttributes = Object.entries(serverSideResult.html.head.attributes)
115
+ .map(([k, v]) => `${k}="${v}"`).join(' ');
116
+ }
117
+ if (serverSideResult.html.body?.attributes) {
118
+ bodyAttributes = Object.entries(serverSideResult.html.body.attributes)
119
+ .map(([k, v]) => `${k}="${v}"`).join(' ');
120
+ }
121
+ if (serverSideResult.html.head?.extraTags) {
122
+ headContent += '\n' + serverSideResult.html.head.extraTags.join('\n');
123
+ }
124
+ if (serverSideResult.title || serverSideResult.html.head?.title) {
125
+ pageTitle = `<title>${serverSideResult.title || serverSideResult.html.head?.title}</title>`;
126
+ }
127
+ }
128
+ let html = template;
129
+ if (htmlAttributes)
130
+ html = html.replace('<html', `<html ${htmlAttributes}`);
131
+ if (bodyAttributes)
132
+ html = html.replace('<body', `<body ${bodyAttributes}`);
133
+ if (pageTitle)
134
+ html = html.replace(/<title>.*?<\/title>/i, pageTitle);
135
+ html = html
136
+ .replace('<!--app-head-->', headContent)
99
137
  .replace('<!--app-html-->', rendered.html)
100
138
  .replace('</body>', `<script>
101
139
  window.__INITIAL_PROPS__=${JSON.stringify(rendered.props || {})};