@ressjs/vite-router 0.4.2 → 0.5.2
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 +445 -70
- package/dist/helpers/html-generator.js +12 -2
- package/dist/helpers/middlewares.js +3 -1
- package/dist/pages.d.ts +9 -0
- package/dist/pages.js +22 -3
- package/dist/render.js +16 -4
- package/dist/router.js +6 -6
- 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
|
-
MIT
|
|
531
|
+
MIT
|
|
@@ -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):
|
|
@@ -367,9 +367,11 @@ async function loadMiddlewareModule(middlewareInfo, vite) {
|
|
|
367
367
|
}
|
|
368
368
|
else {
|
|
369
369
|
// En producción, usar archivos compilados
|
|
370
|
+
// Vite transforms [param] to _param_ in compiled filenames
|
|
370
371
|
const compiledPath = middlewareInfo.file
|
|
371
372
|
.replace(/^app\/pages\//, 'dist/server/')
|
|
372
|
-
.replace(/\.ts$/, '.js')
|
|
373
|
+
.replace(/\.ts$/, '.js')
|
|
374
|
+
.replace(/\[(\w+)\]/g, '_$1_'); // [photocardId] -> _photocardId_
|
|
373
375
|
const moduleUrl = node_path_1.default.resolve(compiledPath);
|
|
374
376
|
moduleToLoad = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
|
|
375
377
|
}
|
package/dist/pages.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import type { PlatformInfo } from './platform';
|
|
2
2
|
export interface PageInfo {
|
|
3
3
|
route: string;
|
|
4
|
+
expressRoute: string;
|
|
4
5
|
file: string;
|
|
5
6
|
abs: string;
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Convierte rutas estilo Next.js con parámetros dinámicos a formato Express
|
|
10
|
+
* Ejemplos:
|
|
11
|
+
* '/photocard/[idPhotocard]' → '/photocard/:idPhotocard'
|
|
12
|
+
* '/products/[category]/[id]' → '/products/:category/:id'
|
|
13
|
+
* '/home' → '/home' (sin cambios)
|
|
14
|
+
*/
|
|
15
|
+
export declare function convertRouteToExpress(route: string): string;
|
|
7
16
|
export declare function getPages(): PageInfo[];
|
|
8
17
|
export declare function getViteInput(): Record<string, string>;
|
|
9
18
|
export declare function getViteSSRInput(): Record<string, string>;
|
package/dist/pages.js
CHANGED
|
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.convertRouteToExpress = convertRouteToExpress;
|
|
39
40
|
exports.getPages = getPages;
|
|
40
41
|
exports.getViteInput = getViteInput;
|
|
41
42
|
exports.getViteSSRInput = getViteSSRInput;
|
|
@@ -48,6 +49,16 @@ exports.getClientEntriesInput = getClientEntriesInput;
|
|
|
48
49
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
49
50
|
const node_path_1 = __importDefault(require("node:path"));
|
|
50
51
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
52
|
+
/**
|
|
53
|
+
* Convierte rutas estilo Next.js con parámetros dinámicos a formato Express
|
|
54
|
+
* Ejemplos:
|
|
55
|
+
* '/photocard/[idPhotocard]' → '/photocard/:idPhotocard'
|
|
56
|
+
* '/products/[category]/[id]' → '/products/:category/:id'
|
|
57
|
+
* '/home' → '/home' (sin cambios)
|
|
58
|
+
*/
|
|
59
|
+
function convertRouteToExpress(route) {
|
|
60
|
+
return route.replace(/\[([^\]]+)\]/g, ':$1');
|
|
61
|
+
}
|
|
51
62
|
function getPages() {
|
|
52
63
|
// Busca tanto page.tsx como page/index.tsx y prioriza el más corto
|
|
53
64
|
const files = fast_glob_1.default.sync('app/pages/**/*.{tsx,jsx}');
|
|
@@ -59,7 +70,8 @@ function getPages() {
|
|
|
59
70
|
// Prioriza page.tsx sobre page/index.tsx
|
|
60
71
|
if (!seen.has(route)) {
|
|
61
72
|
seen.add(route);
|
|
62
|
-
|
|
73
|
+
const expressRoute = convertRouteToExpress(route);
|
|
74
|
+
pages.push({ route, expressRoute, file, abs: node_path_1.default.resolve(file) });
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
return pages;
|
|
@@ -119,9 +131,11 @@ function getCompiledProdPage(pageFile) {
|
|
|
119
131
|
relativePath = pageFile;
|
|
120
132
|
}
|
|
121
133
|
// Convertir app/pages/pokedex/index.tsx a dist/server/pokedex/index.js
|
|
134
|
+
// También convierte [id] a _id_ para rutas dinámicas
|
|
122
135
|
const compiledRelativePath = relativePath
|
|
123
136
|
.replace(/^app\/pages\//, '') // Quitar prefijo app/pages/
|
|
124
|
-
.replace(/\.tsx$/, '.js')
|
|
137
|
+
.replace(/\.tsx$/, '.js') // Cambiar extensión .tsx a .js
|
|
138
|
+
.replace(/\[([^\]]+)\]/g, '_$1_'); // Convertir [id] a _id_
|
|
125
139
|
return node_path_1.default.resolve(process.cwd(), 'dist', 'server', compiledRelativePath);
|
|
126
140
|
}
|
|
127
141
|
async function fileExists(file) {
|
|
@@ -185,7 +199,12 @@ async function generateTempEntries(distPath = 'dist', platformInfo) {
|
|
|
185
199
|
// Solo crea el directorio si no existe, no lo borra
|
|
186
200
|
await promises_1.default.mkdir(entriesDir, { recursive: true });
|
|
187
201
|
const promises = pages.map(async (page) => {
|
|
188
|
-
|
|
202
|
+
// Sanitizar nombre: convertir [id] a _id_ para nombres de archivo
|
|
203
|
+
const rel = page.file
|
|
204
|
+
.replace(/^app\/pages\//, '')
|
|
205
|
+
.replace(/\.tsx$|\.jsx$/, '')
|
|
206
|
+
.replace(/\//g, '__')
|
|
207
|
+
.replace(/\[([^\]]+)\]/g, '_$1_');
|
|
189
208
|
if (platformInfo && platformInfo.device !== 'desktop') {
|
|
190
209
|
// Solo genera entry específico para mobile devices (phone, tablet)
|
|
191
210
|
const basePath = page.file.replace(/\.(tsx|jsx)$/, '');
|
package/dist/render.js
CHANGED
|
@@ -61,11 +61,23 @@ function createRenderServer(options = {}) {
|
|
|
61
61
|
return async (renderOptions) => {
|
|
62
62
|
const { url, props = {} } = renderOptions;
|
|
63
63
|
// Encuentra la página que coincide con la URL
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Función helper para hacer matching de rutas dinámicas
|
|
65
|
+
const matchRoute = (pageRoute, url) => {
|
|
66
|
+
if (pageRoute === '/')
|
|
66
67
|
return url === '/' || url === '';
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// Si no tiene parámetros dinámicos, hacer matching exacto
|
|
69
|
+
if (!pageRoute.includes('[')) {
|
|
70
|
+
return url === pageRoute || url.startsWith(pageRoute + '/');
|
|
71
|
+
}
|
|
72
|
+
// Convertir ruta de página a regex para matching dinámico
|
|
73
|
+
// Ejemplo: /photocard/[id] → /^\/photocard\/([^\/]+)$/
|
|
74
|
+
const regexPattern = pageRoute
|
|
75
|
+
.replace(/\[([^\]]+)\]/g, '([^/]+)') // [id] → ([^/]+)
|
|
76
|
+
.replace(/\//g, '\\/'); // escapar slashes
|
|
77
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
78
|
+
return regex.test(url);
|
|
79
|
+
};
|
|
80
|
+
const matchedPage = pages.find(page => matchRoute(page.route, url));
|
|
69
81
|
if (!matchedPage) {
|
|
70
82
|
throw new Error(`Page not found for URL: ${url}`);
|
|
71
83
|
}
|
package/dist/router.js
CHANGED
|
@@ -42,7 +42,8 @@ async function createAutoViteServer(options = {}) {
|
|
|
42
42
|
console.log(`🚀 Auto Vite Server started at http://localhost:${port}`);
|
|
43
43
|
console.log(`📄 Auto-detected routes:`);
|
|
44
44
|
pages.forEach(page => {
|
|
45
|
-
|
|
45
|
+
const expressDisplay = page.route !== page.expressRoute ? ` (Express: ${page.expressRoute})` : '';
|
|
46
|
+
console.log(` ${page.route}${expressDisplay} -> ${page.file}`);
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
});
|
|
@@ -79,7 +80,6 @@ function createAutoRouter(options) {
|
|
|
79
80
|
return viteConfig;
|
|
80
81
|
};
|
|
81
82
|
const pages = (0, pages_1.getPages)();
|
|
82
|
-
// TU PSEUDOCÓDIGO IMPLEMENTADO CORRECTAMENTE:
|
|
83
83
|
// Initialize Vite ONCE before setting up any routes
|
|
84
84
|
const setupRoutes = async () => {
|
|
85
85
|
if (!viteConfig)
|
|
@@ -104,9 +104,9 @@ function createAutoRouter(options) {
|
|
|
104
104
|
HMRMiddleware,
|
|
105
105
|
renderMiddleware
|
|
106
106
|
];
|
|
107
|
-
mainRouter.get(page.
|
|
107
|
+
mainRouter.get(page.expressRoute, ...pageMiddlewares);
|
|
108
108
|
// Error middleware separado
|
|
109
|
-
mainRouter.use(page.
|
|
109
|
+
mainRouter.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, viteConfig?.vite));
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
// Setup routes asynchronously but don't block router creation
|
|
@@ -145,9 +145,9 @@ function createManualRouter(options) {
|
|
|
145
145
|
HMRMiddleware,
|
|
146
146
|
renderMiddleware
|
|
147
147
|
];
|
|
148
|
-
router.get(page.
|
|
148
|
+
router.get(page.expressRoute, ...pageMiddlewares);
|
|
149
149
|
// Error middleware separado
|
|
150
|
-
router.use(page.
|
|
150
|
+
router.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, vite));
|
|
151
151
|
}
|
|
152
152
|
};
|
|
153
153
|
// Setup routes asynchronously but don't block router creation
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ressjs/vite-router",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Express router factory for ress.js with automatic page detection and
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "Express router factory for ress.js with automatic page detection, SSR and dynamic routes support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"publishConfig": {
|