@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/dist/pages.js CHANGED
@@ -36,19 +36,29 @@ 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;
42
+ exports.getViteSSRInput = getViteSSRInput;
41
43
  exports.loadManifest = loadManifest;
44
+ exports.getCompiledProdPage = getCompiledProdPage;
42
45
  exports.resolvePlatformCSS = resolvePlatformCSS;
43
46
  exports.generateTempEntries = generateTempEntries;
44
47
  exports.generateSingleEntry = generateSingleEntry;
45
48
  exports.getClientEntriesInput = getClientEntriesInput;
46
- exports.createRenderServer = createRenderServer;
47
49
  const promises_1 = __importDefault(require("node:fs/promises"));
48
50
  const node_path_1 = __importDefault(require("node:path"));
49
51
  const fast_glob_1 = __importDefault(require("fast-glob"));
50
- const react_1 = __importDefault(require("react"));
51
- const server_1 = require("react-dom/server");
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
+ }
52
62
  function getPages() {
53
63
  // Busca tanto page.tsx como page/index.tsx y prioriza el más corto
54
64
  const files = fast_glob_1.default.sync('app/pages/**/*.{tsx,jsx}');
@@ -60,7 +70,8 @@ function getPages() {
60
70
  // Prioriza page.tsx sobre page/index.tsx
61
71
  if (!seen.has(route)) {
62
72
  seen.add(route);
63
- pages.push({ route, file, abs: node_path_1.default.resolve(file) });
73
+ const expressRoute = convertRouteToExpress(route);
74
+ pages.push({ route, expressRoute, file, abs: node_path_1.default.resolve(file) });
64
75
  }
65
76
  }
66
77
  return pages;
@@ -74,6 +85,17 @@ function getViteInput() {
74
85
  }
75
86
  return input;
76
87
  }
88
+ // Para Vite SSR: input incluyendo páginas + middlewares
89
+ function getViteSSRInput() {
90
+ const input = getViteInput(); // Empezar con las páginas
91
+ // Agregar TODOS los middlewares: .middlewares.ts y middlewares.ts
92
+ const middlewareFiles = fast_glob_1.default.sync('app/pages/**/{middlewares,*.middlewares}.{ts,js}');
93
+ for (const middlewareFile of middlewareFiles) {
94
+ const key = middlewareFile.replace(/^app\/pages\//, '').replace(/\.(ts|js)$/, '');
95
+ input[key] = node_path_1.default.resolve(middlewareFile);
96
+ }
97
+ return input;
98
+ }
77
99
  async function loadManifest(distPath = 'dist') {
78
100
  const assetsDir = node_path_1.default.join(distPath, 'server/.vite');
79
101
  const files = await promises_1.default.readdir(assetsDir);
@@ -84,6 +106,38 @@ async function loadManifest(distPath = 'dist') {
84
106
  const manifest = (await Promise.resolve(`${node_path_1.default.join(assetsDir, manifestFile)}`).then(s => __importStar(require(s)))).default;
85
107
  return manifest;
86
108
  }
109
+ /**
110
+ * Convierte una ruta de página TypeScript a su equivalente compilado en producción
111
+ *
112
+ * @param pageFile - Ruta del archivo de página (relativa: "app/pages/pokedex/index.tsx" o absoluta: "/path/to/app/pages/pokedex/index.tsx")
113
+ * @returns Ruta absoluta del archivo compilado (ej: "/path/to/project/dist/server/pokedex/index.js")
114
+ */
115
+ function getCompiledProdPage(pageFile) {
116
+ if (!pageFile.endsWith('.tsx')) {
117
+ throw new Error(`getCompiledProdPage expects a .tsx file, got: ${pageFile}`);
118
+ }
119
+ // Si es una ruta absoluta, extraer solo la parte relativa
120
+ let relativePath;
121
+ if (node_path_1.default.isAbsolute(pageFile)) {
122
+ // Buscar la parte después de "app/pages/" or "app\pages\" (Windows-compatible)
123
+ const normalizedPath = pageFile.replace(/\\/g, '/');
124
+ const appPagesIndex = normalizedPath.indexOf('app/pages/');
125
+ if (appPagesIndex === -1) {
126
+ throw new Error(`Absolute path must contain "app/pages/", got: ${pageFile}`);
127
+ }
128
+ relativePath = normalizedPath.substring(appPagesIndex);
129
+ }
130
+ else {
131
+ relativePath = pageFile;
132
+ }
133
+ // Convertir app/pages/pokedex/index.tsx a dist/server/pokedex/index.js
134
+ // También convierte [id] a _id_ para rutas dinámicas
135
+ const compiledRelativePath = relativePath
136
+ .replace(/^app\/pages\//, '') // Quitar prefijo app/pages/
137
+ .replace(/\.tsx$/, '.js') // Cambiar extensión .tsx a .js
138
+ .replace(/\[([^\]]+)\]/g, '_$1_'); // Convertir [id] a _id_
139
+ return node_path_1.default.resolve(process.cwd(), 'dist', 'server', compiledRelativePath);
140
+ }
87
141
  async function fileExists(file) {
88
142
  try {
89
143
  await promises_1.default.access(file);
@@ -145,7 +199,12 @@ async function generateTempEntries(distPath = 'dist', platformInfo) {
145
199
  // Solo crea el directorio si no existe, no lo borra
146
200
  await promises_1.default.mkdir(entriesDir, { recursive: true });
147
201
  const promises = pages.map(async (page) => {
148
- const rel = page.file.replace(/^app\/pages\//, '').replace(/\.tsx$|\.jsx$/, '').replace(/\//g, '__');
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_');
149
208
  if (platformInfo && platformInfo.device !== 'desktop') {
150
209
  // Solo genera entry específico para mobile devices (phone, tablet)
151
210
  const basePath = page.file.replace(/\.(tsx|jsx)$/, '');
@@ -238,59 +297,3 @@ async function getClientEntriesInput(distPath = 'dist') {
238
297
  }
239
298
  return input;
240
299
  }
241
- function createRenderServer(options = {}) {
242
- const pages = getPages();
243
- const { vite } = options;
244
- return async (renderOptions) => {
245
- const { url, props = {} } = renderOptions;
246
- // Encuentra la página que coincide con la URL
247
- const matchedPage = pages.find(page => {
248
- if (page.route === '/')
249
- return url === '/' || url === '';
250
- return url === page.route || url.startsWith(page.route + '/');
251
- });
252
- if (!matchedPage) {
253
- throw new Error(`Page not found for URL: ${url}`);
254
- }
255
- try {
256
- // Import dinámico de la página usando vite.ssrLoadModule si está disponible
257
- let pageModule;
258
- if (vite) {
259
- // En desarrollo: usar vite.ssrLoadModule para manejar .tsx
260
- const modulePath = `/${matchedPage.file}`;
261
- pageModule = await vite.ssrLoadModule(modulePath);
262
- }
263
- else {
264
- // En producción: usar import directo del archivo compilado
265
- const moduleUrl = matchedPage.abs.replace(/\.tsx$/, '.js').replace('/app/pages/', '/dist/server/');
266
- pageModule = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
267
- }
268
- const PageComponent = pageModule.default;
269
- const getServerSideProps = pageModule.getServerSideProps;
270
- if (!PageComponent) {
271
- throw new Error(`No default export found in ${matchedPage.file}`);
272
- }
273
- // Si existe getServerSideProps, la ejecutamos para obtener props iniciales
274
- let finalProps = { ...props };
275
- if (getServerSideProps && typeof getServerSideProps === 'function') {
276
- console.log(`Calling getServerSideProps for ${matchedPage.route}`);
277
- const serverSideResult = await getServerSideProps();
278
- if (serverSideResult && serverSideResult.props) {
279
- finalProps = { ...finalProps, ...serverSideResult.props };
280
- }
281
- }
282
- // Renderiza el componente con las props finales
283
- const html = (0, server_1.renderToString)(react_1.default.createElement(PageComponent, finalProps));
284
- return {
285
- html,
286
- props: finalProps,
287
- route: matchedPage.route,
288
- page: matchedPage.file
289
- };
290
- }
291
- catch (error) {
292
- console.error(`Error rendering page ${matchedPage.route}:`, error);
293
- throw error;
294
- }
295
- };
296
- }
@@ -14,11 +14,3 @@ export interface PlatformInfo {
14
14
  * Detecta plataforma usando headers custom + User-Agent fallback
15
15
  */
16
16
  export declare function detectPlatform(userAgent: string, headers?: any): PlatformInfo;
17
- /**
18
- * Genera CSS class para HTML basado en platform info
19
- */
20
- export declare function getPlatformCSSClass(platform: PlatformInfo): string;
21
- /**
22
- * Determina qué entry-client usar basado en platform
23
- */
24
- export declare function resolveEntryClient(baseName: string, platform: PlatformInfo): string;
package/dist/platform.js CHANGED
@@ -5,8 +5,6 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.detectPlatform = detectPlatform;
8
- exports.getPlatformCSSClass = getPlatformCSSClass;
9
- exports.resolveEntryClient = resolveEntryClient;
10
8
  /**
11
9
  * Detecta plataforma usando headers custom + User-Agent fallback
12
10
  */
@@ -106,38 +104,3 @@ function detectFromUserAgent(userAgent) {
106
104
  source: 'user-agent'
107
105
  };
108
106
  }
109
- /**
110
- * Genera CSS class para HTML basado en platform info
111
- */
112
- function getPlatformCSSClass(platform) {
113
- const classes = [
114
- platform.category, // 'web', 'webview', 'native'
115
- platform.device, // 'phone', 'tablet', 'desktop'
116
- platform.os // 'ios', 'android', etc.
117
- ];
118
- return classes.join(' ');
119
- }
120
- /**
121
- * Determina qué entry-client usar basado en platform
122
- */
123
- function resolveEntryClient(baseName, platform) {
124
- const candidates = [
125
- // Específico para mobile: device.os (phone.ios, tablet.android)
126
- ...(platform.device !== 'desktop'
127
- ? [`${baseName}__entry-client.${platform.device}.${platform.os}.tsx`]
128
- : []),
129
- // OS específico: ios, android
130
- `${baseName}__entry-client.${platform.os}.tsx`,
131
- // Device: mobile, desktop, tablet
132
- `${baseName}__entry-client.${platform.device}.tsx`,
133
- // Legacy compatibility
134
- ...(platform.device === 'phone' || platform.device === 'tablet'
135
- ? [`${baseName}__entry-client.mobile.tsx`]
136
- : []),
137
- // Fallback
138
- `${baseName}__entry-client.tsx`
139
- ];
140
- // En producción, deberías verificar que el archivo existe
141
- // Por ahora retorna el primer candidato
142
- return candidates[0];
143
- }
package/dist/render.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { RequestHandler } from 'express';
2
+ /**
3
+ * RENDER MIDDLEWARE PARA EL ROUTER
4
+ */
5
+ export declare function createRenderMiddleware(page: any, options: any): RequestHandler;
1
6
  export interface RenderOptions {
2
7
  url: string;
3
8
  props?: any;
package/dist/render.js CHANGED
@@ -36,21 +36,48 @@ 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.createRenderMiddleware = createRenderMiddleware;
39
40
  exports.createRenderServer = createRenderServer;
40
41
  const react_1 = __importDefault(require("react"));
41
42
  const server_1 = require("react-dom/server");
42
43
  const pages_1 = require("./pages");
44
+ const request_handler_1 = require("./helpers/request-handler");
45
+ /**
46
+ * RENDER MIDDLEWARE PARA EL ROUTER
47
+ */
48
+ function createRenderMiddleware(page, options) {
49
+ return async (req, res, next) => {
50
+ try {
51
+ await (0, request_handler_1.processRequest)({ req, res, page, next }, options);
52
+ }
53
+ catch (error) {
54
+ next(error);
55
+ }
56
+ };
57
+ }
43
58
  function createRenderServer(options = {}) {
44
59
  const pages = (0, pages_1.getPages)();
45
60
  const { vite } = options;
46
61
  return async (renderOptions) => {
47
62
  const { url, props = {} } = renderOptions;
48
63
  // Encuentra la página que coincide con la URL
49
- const matchedPage = pages.find(page => {
50
- if (page.route === '/')
64
+ // Función helper para hacer matching de rutas dinámicas
65
+ const matchRoute = (pageRoute, url) => {
66
+ if (pageRoute === '/')
51
67
  return url === '/' || url === '';
52
- return url === page.route || url.startsWith(page.route + '/');
53
- });
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));
54
81
  if (!matchedPage) {
55
82
  throw new Error(`Page not found for URL: ${url}`);
56
83
  }
@@ -64,28 +91,34 @@ function createRenderServer(options = {}) {
64
91
  }
65
92
  else {
66
93
  // En producción: usar import directo del archivo compilado
67
- const moduleUrl = matchedPage.abs.replace(/\.tsx$/, '.js').replace('/app/pages/', '/dist/server/');
68
- pageModule = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
94
+ const moduleUrl = (0, pages_1.getCompiledProdPage)(matchedPage.abs);
95
+ try {
96
+ pageModule = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
97
+ }
98
+ catch (importError) {
99
+ console.error('Error loading module:', importError);
100
+ throw importError;
101
+ }
69
102
  }
70
103
  const PageComponent = pageModule.default;
71
- const getServerSideProps = pageModule.getServerSideProps;
72
104
  if (!PageComponent) {
73
105
  throw new Error(`No default export found in ${matchedPage.file}`);
74
106
  }
75
- // Si existe getServerSideProps, la ejecutamos para obtener props iniciales
76
- let finalProps = { ...props };
77
- if (getServerSideProps && typeof getServerSideProps === 'function') {
78
- console.log(`Calling getServerSideProps for ${matchedPage.route}`);
79
- const serverSideResult = await getServerSideProps();
80
- if (serverSideResult && serverSideResult.props) {
81
- finalProps = { ...finalProps, ...serverSideResult.props };
82
- }
83
- }
107
+ // Los middlewares ya se ejecutaron antes de llegar aquí.
108
+ // `props` contiene todo lo que los middlewares pusieron en `res.locals`.
109
+ // Combinamos todo para SSR y luego filtramos lo sensible para el cliente.
110
+ const finalProps = {
111
+ ...props,
112
+ ...(props.serverSideProps || {})
113
+ };
84
114
  // Renderiza el componente con las props finales
85
115
  const html = (0, server_1.renderToString)(react_1.default.createElement(PageComponent, finalProps));
116
+ // Evitar mandar htmlConfig al cliente
117
+ const { htmlConfig, serverSideProps, ...rest } = finalProps;
118
+ const clientProps = serverSideProps || rest;
86
119
  return {
87
120
  html,
88
- props: finalProps,
121
+ props: clientProps,
89
122
  route: matchedPage.route,
90
123
  page: matchedPage.file
91
124
  };
package/dist/router.js CHANGED
@@ -11,7 +11,8 @@ const express_1 = require("express");
11
11
  const pages_1 = require("./pages");
12
12
  const express_2 = __importDefault(require("express"));
13
13
  const vite_config_1 = require("./helpers/vite-config");
14
- const request_handler_1 = require("./helpers/request-handler");
14
+ const middlewares_1 = require("./helpers/middlewares");
15
+ const render_1 = require("./render");
15
16
  async function createAutoViteServer(options = {}) {
16
17
  const { port = 5173, base = '/', enablePlatformDetection = true } = options;
17
18
  const isProduction = process.env.NODE_ENV === 'production';
@@ -36,12 +37,15 @@ async function createAutoViteServer(options = {}) {
36
37
  app.use(express_2.default.static('public'));
37
38
  const start = () => {
38
39
  return app.listen(port, () => {
39
- const pages = (0, pages_1.getPages)();
40
- console.log(`🚀 Auto Vite Server started at http://localhost:${port}`);
41
- console.log(`📄 Auto-detected routes:`);
42
- pages.forEach(page => {
43
- console.log(` ${page.route} -> ${page.file}`);
44
- });
40
+ if (process.env.NODE_ENV !== 'production') {
41
+ const pages = (0, pages_1.getPages)();
42
+ console.log(`🚀 Auto Vite Server started at http://localhost:${port}`);
43
+ console.log(`📄 Auto-detected routes:`);
44
+ pages.forEach(page => {
45
+ const expressDisplay = page.route !== page.expressRoute ? ` (Express: ${page.expressRoute})` : '';
46
+ console.log(` ${page.route}${expressDisplay} -> ${page.file}`);
47
+ });
48
+ }
45
49
  });
46
50
  };
47
51
  return { app, vite: viteConfig.vite, start };
@@ -54,10 +58,9 @@ function createViteRouter(options = {}) {
54
58
  return createManualRouter(options);
55
59
  }
56
60
  function createAutoRouter(options) {
57
- const { enablePlatformDetection = true, renderFunction } = options;
61
+ const { renderFunction, enablePlatformDetection = true } = options;
58
62
  const isProduction = process.env.NODE_ENV === 'production';
59
63
  let viteConfig = null;
60
- let renderServer = null;
61
64
  let initialized = false;
62
65
  const mainRouter = (0, express_1.Router)();
63
66
  const initializeIfNeeded = async () => {
@@ -77,38 +80,78 @@ function createAutoRouter(options) {
77
80
  return viteConfig;
78
81
  };
79
82
  const pages = (0, pages_1.getPages)();
80
- pages.forEach(page => {
81
- mainRouter.get(page.route, async (req, res, next) => {
83
+ // Initialize Vite ONCE before setting up any routes
84
+ const setupRoutes = async () => {
85
+ if (!viteConfig)
82
86
  await initializeIfNeeded();
83
- await (0, request_handler_1.processRequest)({ req, res, page, next }, {
87
+ // Process pages sequentially to avoid race conditions
88
+ for (const page of pages) {
89
+ // Dynamic middleware loading for HMR support
90
+ const HMRMiddleware = await (0, middlewares_1.createDynamicPageMiddlewares)(page.route, page.file, viteConfig?.vite);
91
+ // Productive middlewares
92
+ const prevMiddlewares = await (0, middlewares_1.loadPageMiddlewares)(page.route, viteConfig?.vite);
93
+ const serverSidePropsMiddleware = await (0, middlewares_1.getServerSidePropsAsMiddleware)(page.file, viteConfig?.vite);
94
+ const renderMiddleware = (0, render_1.createRenderMiddleware)(page, {
84
95
  enablePlatformDetection,
85
96
  renderFunction,
86
- viteConfig
87
- }, renderServer);
88
- });
89
- });
97
+ viteConfig: () => viteConfig // pasar por función para evitar capturar null
98
+ });
99
+ const pageMiddlewares = isProduction ? [
100
+ ...prevMiddlewares,
101
+ serverSidePropsMiddleware,
102
+ renderMiddleware
103
+ ] : [
104
+ HMRMiddleware,
105
+ renderMiddleware
106
+ ];
107
+ mainRouter.get(page.expressRoute, ...pageMiddlewares);
108
+ // Error middleware separado
109
+ mainRouter.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, viteConfig?.vite));
110
+ }
111
+ };
112
+ // Setup routes asynchronously but don't block router creation
113
+ setupRoutes().catch(console.error);
90
114
  return mainRouter;
91
115
  }
92
116
  function createManualRouter(options) {
93
- const { enablePlatformDetection = true, vite, templateHtml = '', isProduction = process.env.NODE_ENV === 'production', renderFunction } = options;
117
+ const { vite, renderFunction, enablePlatformDetection = true } = options;
94
118
  const router = (0, express_1.Router)();
95
119
  const pages = (0, pages_1.getPages)();
96
- let renderServer = null;
120
+ // Crear viteConfig para processRequest
97
121
  const viteConfig = {
98
122
  vite,
99
- manifest: {},
100
- templateHtml,
101
- isProduction
123
+ templateHtml: options.templateHtml || '',
124
+ isProduction: options.isProduction || false,
125
+ manifest: null
102
126
  };
103
- pages.forEach(page => {
104
- router.get(page.route, async (req, res, next) => {
105
- await (0, request_handler_1.processRequest)({ req, res, page, next }, {
127
+ // Process pages sequentially to avoid race conditions
128
+ const setupRoutes = async () => {
129
+ for (const page of pages) {
130
+ // Dynamic middleware loading for HMR support(only in dev)
131
+ const HMRMiddleware = await (0, middlewares_1.createDynamicPageMiddlewares)(page.route, page.file, vite);
132
+ // Productive middlewares
133
+ const prevMiddlewares = await (0, middlewares_1.loadPageMiddlewares)(page.route, vite);
134
+ const serverSidePropsMiddleware = await (0, middlewares_1.getServerSidePropsAsMiddleware)(page.file, vite);
135
+ const renderMiddleware = (0, render_1.createRenderMiddleware)(page, {
106
136
  enablePlatformDetection,
107
137
  renderFunction,
108
138
  viteConfig
109
- }, renderServer);
110
- });
111
- });
139
+ });
140
+ const pageMiddlewares = options.isProduction ? [
141
+ ...prevMiddlewares,
142
+ serverSidePropsMiddleware,
143
+ renderMiddleware
144
+ ] : [
145
+ HMRMiddleware,
146
+ renderMiddleware
147
+ ];
148
+ router.get(page.expressRoute, ...pageMiddlewares);
149
+ // Error middleware separado
150
+ router.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, vite));
151
+ }
152
+ };
153
+ // Setup routes asynchronously but don't block router creation
154
+ setupRoutes().catch(console.error);
112
155
  return router;
113
156
  }
114
157
  function listDetectedPages() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ressjs/vite-router",
3
- "version": "0.4.1",
4
- "description": "Express router factory for ress.js with automatic page detection and SSR",
3
+ "version": "0.5.0",
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": {