@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 +444 -69
- package/dist/helpers/html-generator.d.ts +1 -0
- package/dist/helpers/html-generator.js +43 -5
- package/dist/helpers/middlewares.d.ts +72 -0
- package/dist/helpers/middlewares.js +381 -0
- package/dist/helpers/request-handler.js +21 -4
- package/dist/helpers/vite-config.d.ts +1 -1
- package/dist/helpers/vite-config.js +8 -15
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -4
- package/dist/pages.d.ts +17 -13
- package/dist/pages.js +64 -61
- package/dist/platform.d.ts +0 -8
- package/dist/platform.js +0 -37
- package/dist/render.d.ts +5 -0
- package/dist/render.js +50 -17
- package/dist/router.js +70 -27
- package/package.json +2 -2
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
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
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
|
|
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
|
-
|
|
227
|
+
### Route Examples
|
|
52
228
|
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
###
|
|
260
|
+
### Dynamic Route Styles
|
|
61
261
|
|
|
62
|
-
|
|
262
|
+
Dynamic routes support platform-specific styles:
|
|
63
263
|
|
|
64
264
|
```
|
|
65
|
-
app/pages/
|
|
66
|
-
app/pages/
|
|
67
|
-
app/pages/
|
|
68
|
-
app/pages/
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
296
|
+
### Middleware Types
|
|
79
297
|
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
100
|
-
|
|
101
|
-
Generates complete HTML with proper asset injection and hydration.
|
|
446
|
+
### createAutoViteServer(options)
|
|
102
447
|
|
|
103
448
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
##
|
|
473
|
+
## 🔧 Production Build
|
|
126
474
|
|
|
127
|
-
|
|
475
|
+
### Build Process
|
|
128
476
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
488
|
+
### Example Production Server
|
|
142
489
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
490
|
+
```typescript
|
|
491
|
+
import express from 'express'
|
|
492
|
+
import { createViteRouter } from '@ressjs/vite-router'
|
|
493
|
+
|
|
494
|
+
const app = express()
|
|
146
495
|
|
|
147
|
-
|
|
148
|
-
|
|
496
|
+
// Serve static assets
|
|
497
|
+
app.use('/assets', express.static('dist/client/assets'))
|
|
149
498
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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 || {})};
|