@ressjs/vite-router 0.5.2 → 0.6.0-rc.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.
Files changed (141) hide show
  1. package/LICENSE +69 -0
  2. package/README.md +9 -515
  3. package/dist/client/entry.d.mts +2 -0
  4. package/dist/client/entry.mjs +1 -0
  5. package/dist/client/index.d.ts +2 -0
  6. package/dist/client/index.js +8 -0
  7. package/dist/config/base-path.d.ts +44 -0
  8. package/dist/config/base-path.js +100 -0
  9. package/dist/config/codegen.d.ts +29 -0
  10. package/dist/config/codegen.js +131 -0
  11. package/dist/config/define.d.ts +8 -0
  12. package/dist/config/define.js +12 -0
  13. package/dist/config/env.d.ts +19 -0
  14. package/dist/config/env.js +40 -0
  15. package/dist/config/index.d.ts +18 -0
  16. package/dist/config/index.js +46 -0
  17. package/dist/config/load.d.ts +28 -0
  18. package/dist/config/load.js +82 -0
  19. package/dist/config/middleware.d.ts +18 -0
  20. package/dist/config/middleware.js +79 -0
  21. package/dist/config/rules.d.ts +59 -0
  22. package/dist/config/rules.js +162 -0
  23. package/dist/config/types.d.ts +189 -0
  24. package/dist/config/types.js +2 -0
  25. package/dist/config/validate.d.ts +19 -0
  26. package/dist/config/validate.js +143 -0
  27. package/dist/config/watch.d.ts +53 -0
  28. package/dist/config/watch.js +163 -0
  29. package/dist/fs/module-extensions.d.ts +105 -0
  30. package/dist/fs/module-extensions.js +213 -0
  31. package/dist/head/resolve.d.ts +5 -0
  32. package/dist/head/resolve.js +166 -0
  33. package/dist/head/types.d.ts +44 -0
  34. package/dist/head/types.js +2 -0
  35. package/dist/helpers/html-generator.d.ts +26 -6
  36. package/dist/helpers/html-generator.js +96 -138
  37. package/dist/helpers/middlewares.d.ts +32 -21
  38. package/dist/helpers/middlewares.js +181 -159
  39. package/dist/helpers/page-config-merge.d.ts +11 -0
  40. package/dist/helpers/page-config-merge.js +84 -0
  41. package/dist/helpers/request-handler.d.ts +15 -2
  42. package/dist/helpers/request-handler.js +164 -27
  43. package/dist/index.d.ts +35 -3
  44. package/dist/index.js +77 -8
  45. package/dist/isr/capture.d.ts +79 -0
  46. package/dist/isr/capture.js +222 -0
  47. package/dist/isr/handler.d.ts +49 -0
  48. package/dist/isr/handler.js +207 -0
  49. package/dist/isr/key.d.ts +26 -0
  50. package/dist/isr/key.js +59 -0
  51. package/dist/isr/page-config.d.ts +17 -0
  52. package/dist/isr/page-config.js +71 -0
  53. package/dist/isr/preview.d.ts +5 -0
  54. package/dist/isr/preview.js +38 -0
  55. package/dist/isr/public-request.d.ts +50 -0
  56. package/dist/isr/public-request.js +108 -0
  57. package/dist/isr/response.d.ts +41 -0
  58. package/dist/isr/response.js +128 -0
  59. package/dist/isr/store.d.ts +42 -0
  60. package/dist/isr/store.js +108 -0
  61. package/dist/isr/types.d.ts +62 -0
  62. package/dist/isr/types.js +2 -0
  63. package/dist/pages.d.ts +10 -9
  64. package/dist/pages.js +42 -261
  65. package/dist/platform.d.ts +11 -14
  66. package/dist/platform.js +18 -102
  67. package/dist/plugin/environments.d.ts +68 -0
  68. package/dist/plugin/environments.js +74 -0
  69. package/dist/plugin/index.d.ts +59 -0
  70. package/dist/plugin/index.js +195 -0
  71. package/dist/plugin/route-manifest.d.ts +19 -0
  72. package/dist/plugin/route-manifest.js +55 -0
  73. package/dist/plugin/virtual-entries.d.ts +26 -0
  74. package/dist/plugin/virtual-entries.js +80 -0
  75. package/dist/render.d.ts +39 -2
  76. package/dist/render.js +75 -77
  77. package/dist/router.d.ts +32 -13
  78. package/dist/router.js +180 -146
  79. package/dist/routes/dispatcher.d.ts +58 -0
  80. package/dist/routes/dispatcher.js +70 -0
  81. package/dist/routes/manifest.d.ts +17 -0
  82. package/dist/routes/manifest.js +62 -0
  83. package/dist/routes/match.d.ts +20 -0
  84. package/dist/routes/match.js +75 -0
  85. package/dist/routes/module.d.ts +14 -0
  86. package/dist/routes/module.js +30 -0
  87. package/dist/routes/parse.d.ts +31 -0
  88. package/dist/routes/parse.js +114 -0
  89. package/dist/routes/rank.d.ts +12 -0
  90. package/dist/routes/rank.js +49 -0
  91. package/dist/routes/router.d.ts +54 -0
  92. package/dist/routes/router.js +153 -0
  93. package/dist/routes/scan.d.ts +26 -0
  94. package/dist/routes/scan.js +98 -0
  95. package/dist/routes/types.d.ts +114 -0
  96. package/dist/routes/types.js +17 -0
  97. package/dist/runtime/dev-server.d.ts +64 -0
  98. package/dist/runtime/dev-server.js +94 -0
  99. package/dist/runtime/dev-styles.d.ts +13 -0
  100. package/dist/runtime/dev-styles.js +50 -0
  101. package/dist/runtime/module-loader.d.ts +55 -0
  102. package/dist/runtime/module-loader.js +122 -0
  103. package/dist/runtime/prod-server.d.ts +55 -0
  104. package/dist/runtime/prod-server.js +188 -0
  105. package/dist/runtime/template.d.ts +10 -0
  106. package/dist/runtime/template.js +33 -0
  107. package/dist/security/client-props.d.ts +25 -0
  108. package/dist/security/client-props.js +51 -0
  109. package/dist/security/config.d.ts +121 -0
  110. package/dist/security/config.js +77 -0
  111. package/dist/security/csp.d.ts +53 -0
  112. package/dist/security/csp.js +118 -0
  113. package/dist/security/dev-hardening.d.ts +46 -0
  114. package/dist/security/dev-hardening.js +65 -0
  115. package/dist/security/errors.d.ts +37 -0
  116. package/dist/security/errors.js +84 -0
  117. package/dist/security/escape.d.ts +40 -0
  118. package/dist/security/escape.js +90 -0
  119. package/dist/security/head-tags.d.ts +32 -0
  120. package/dist/security/head-tags.js +156 -0
  121. package/dist/security/headers.d.ts +76 -0
  122. package/dist/security/headers.js +278 -0
  123. package/dist/security/index.d.ts +24 -0
  124. package/dist/security/index.js +49 -0
  125. package/dist/security/serialize.d.ts +48 -0
  126. package/dist/security/serialize.js +146 -0
  127. package/dist/variants/assets.d.ts +33 -0
  128. package/dist/variants/assets.js +144 -0
  129. package/dist/variants/catalog.d.ts +33 -0
  130. package/dist/variants/catalog.js +79 -0
  131. package/dist/variants/platform-tokens.d.ts +9 -0
  132. package/dist/variants/platform-tokens.js +15 -0
  133. package/dist/variants/resolve.d.ts +40 -0
  134. package/dist/variants/resolve.js +90 -0
  135. package/dist/variants/suffix.d.ts +8 -0
  136. package/dist/variants/suffix.js +12 -0
  137. package/dist/variants/types.d.ts +72 -0
  138. package/dist/variants/types.js +2 -0
  139. package/package.json +23 -11
  140. package/dist/helpers/vite-config.d.ts +0 -10
  141. package/dist/helpers/vite-config.js +0 -90
package/dist/render.js CHANGED
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -40,15 +7,36 @@ exports.createRenderMiddleware = createRenderMiddleware;
40
7
  exports.createRenderServer = createRenderServer;
41
8
  const react_1 = __importDefault(require("react"));
42
9
  const server_1 = require("react-dom/server");
43
- const pages_1 = require("./pages");
10
+ const node_url_1 = require("node:url");
44
11
  const request_handler_1 = require("./helpers/request-handler");
12
+ const prod_server_1 = require("./runtime/prod-server");
13
+ const security_1 = require("./security");
14
+ const assets_1 = require("@ressjs/assets");
45
15
  /**
46
16
  * RENDER MIDDLEWARE PARA EL ROUTER
17
+ *
18
+ * El render server se arma una sola vez por ruta, en la primera petición, y
19
+ * se reusa en las siguientes. Antes se creaba en cada `processRequest` sin
20
+ * `renderServer`, y `createRenderServer` barría `app/pages` con `fast-glob`
21
+ * en cada request: cien peticiones, cien globs síncronos contra el
22
+ * filesystem en producción.
23
+ *
24
+ * La primera petición espera a que `viteConfig` (que puede llegar como thunk
25
+ * en modo automático) esté resuelto, así que armar el render server recién
26
+ * ahí, y no al declarar el middleware, es lo que garantiza que ya tenga el
27
+ * `moduleLoader` correcto.
47
28
  */
48
29
  function createRenderMiddleware(page, options) {
30
+ let renderServer;
49
31
  return async (req, res, next) => {
50
32
  try {
51
- await (0, request_handler_1.processRequest)({ req, res, page, next }, options);
33
+ // Con `renderFunction` el pipeline de render propio no corre: armar el
34
+ // render server igual sería trabajo tirado (y, en tests, un moduleLoader
35
+ // que no llegó a resolverse).
36
+ if (!renderServer && !options.renderFunction) {
37
+ renderServer = (0, request_handler_1.buildRenderServer)(options);
38
+ }
39
+ await (0, request_handler_1.processRequest)({ req, res, page, next }, options, renderServer);
52
40
  }
53
41
  catch (error) {
54
42
  next(error);
@@ -56,49 +44,29 @@ function createRenderMiddleware(page, options) {
56
44
  };
57
45
  }
58
46
  function createRenderServer(options = {}) {
59
- const pages = (0, pages_1.getPages)();
60
- const { vite } = options;
47
+ const { moduleLoader } = options;
48
+ const policy = options.clientProps ?? (0, security_1.resolveSecurityOptions)().clientProps;
49
+ const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
61
50
  return async (renderOptions) => {
62
- const { url, props = {} } = renderOptions;
63
- // Encuentra la página que coincide con la URL
64
- // Función helper para hacer matching de rutas dinámicas
65
- const matchRoute = (pageRoute, url) => {
66
- if (pageRoute === '/')
67
- return url === '/' || url === '';
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));
81
- if (!matchedPage) {
82
- throw new Error(`Page not found for URL: ${url}`);
51
+ const { url, props = {}, locals = {}, page, hydrate = true } = renderOptions;
52
+ // La página la resuelve el despachador antes de llegar acá (route groups,
53
+ // catch-all, etc.); volver a buscarla por URL con un criterio propio sería
54
+ // una segunda resolución que no conoce esos casos y puede discrepar con
55
+ // la primera.
56
+ if (!page) {
57
+ throw new Error(`createRenderServer: falta \`page\` para la URL "${url}". El despachador tiene ` +
58
+ 'que resolverla antes de invocar el render; no se busca más por glob.');
83
59
  }
60
+ const matchedPage = page;
84
61
  try {
85
- // Import dinámico de la página usando vite.ssrLoadModule si está disponible
86
62
  let pageModule;
87
- if (vite) {
88
- // En desarrollo: usar vite.ssrLoadModule para manejar .tsx
89
- const modulePath = `/${matchedPage.file}`;
90
- pageModule = await vite.ssrLoadModule(modulePath);
63
+ if (moduleLoader) {
64
+ pageModule = await moduleLoader.load(`/${matchedPage.file}`);
91
65
  }
92
66
  else {
93
- // En producción: usar import directo del archivo compilado
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
- }
67
+ // Ruta absoluta: en Windows sólo se puede importar como URL `file://`.
68
+ const moduleUrl = (0, prod_server_1.resolveServerModulePath)(matchedPage.abs ?? matchedPage.file);
69
+ pageModule = await import(/* @vite-ignore */ (0, node_url_1.pathToFileURL)(moduleUrl).href);
102
70
  }
103
71
  const PageComponent = pageModule.default;
104
72
  if (!PageComponent) {
@@ -111,14 +79,26 @@ function createRenderServer(options = {}) {
111
79
  ...props,
112
80
  ...(props.serverSideProps || {})
113
81
  };
114
- // Renderiza el componente con las props finales
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;
82
+ // El collector vive en este árbol, no en un singleton. Dos requests que
83
+ // renderizan a la vez nunca pueden mezclar sus títulos o metadatos.
84
+ const head = [];
85
+ const tree = react_1.default.createElement(assets_1.HeadCollectorProvider, { collector: head }, react_1.default.createElement(PageComponent, finalProps));
86
+ const html = (0, server_1.renderToString)(tree);
87
+ // El componente ya recibió todo arriba. Lo que viaja al cliente es sólo
88
+ // lo que la política declara: enumerar lo que se excluye deja expuesta
89
+ // cada clave que un middleware agregue después.
90
+ //
91
+ // `serverSideProps` es lo que la página pidió explícitamente para el
92
+ // cliente; el resto de `res.locals` es estado del servidor y no viaja
93
+ // salvo que se declare.
94
+ const exposable = { ...locals, serverSideProps: props };
95
+ const clientProps = hydrate ? (0, security_1.pickClientProps)(exposable, policy) : {};
96
+ if (hydrate && !isProduction)
97
+ warnWithheld(matchedPage.route, exposable, clientProps);
119
98
  return {
120
99
  html,
121
100
  props: clientProps,
101
+ head,
122
102
  route: matchedPage.route,
123
103
  page: matchedPage.file
124
104
  };
@@ -129,3 +109,21 @@ function createRenderServer(options = {}) {
129
109
  }
130
110
  };
131
111
  }
112
+ /**
113
+ * Avisa, una vez por ruta, qué quedó fuera del cliente.
114
+ *
115
+ * Una página que renderiza en el servidor con datos que el cliente no tiene
116
+ * hidrata distinto, y ese desajuste no dice de dónde viene. El aviso lo nombra.
117
+ */
118
+ const warnedRoutes = new Set();
119
+ function warnWithheld(route, serverProps, clientProps) {
120
+ if (warnedRoutes.has(route))
121
+ return;
122
+ const withheld = (0, security_1.withheldKeys)(serverProps, clientProps);
123
+ if (withheld.length === 0)
124
+ return;
125
+ warnedRoutes.add(route);
126
+ console.warn(`[ress] ${route}: estas claves existen en el servidor y no viajan al cliente: ` +
127
+ `${withheld.join(', ')}.\n` +
128
+ 'Si la página las usa al hidratar, declaralas en `security.clientProps.expose`.');
129
+ }
package/dist/router.d.ts CHANGED
@@ -1,27 +1,46 @@
1
1
  import { Router } from 'express';
2
+ import type { RessRuntime } from './runtime/dev-server';
3
+ import type { ModuleLoader } from './runtime/module-loader';
4
+ import { type ResolvedSecurityOptions, type SecurityOptions } from './security';
5
+ import { type ResolvedConfig } from './config';
6
+ /** Espera a que el runtime, el manifest y las cadenas de middleware estén listos. */
7
+ export declare function waitForViteRouter(router: Router): Promise<void>;
2
8
  export interface ViteRouterOptions {
3
9
  enablePlatformDetection?: boolean;
4
10
  isProduction?: boolean;
5
- pagesDirectory?: string;
6
11
  basePath?: string;
7
- vite?: any;
12
+ /** Runtime ya construido, para quien lo arma por su cuenta. */
13
+ runtime?: RessRuntime;
14
+ /** Cargador de módulos, para quien arma su propio servidor. */
15
+ moduleLoader?: ModuleLoader;
8
16
  templateHtml?: string;
9
17
  renderFunction?: (options: {
10
18
  url: string;
11
19
  platform: string;
12
20
  req: any;
13
21
  }) => Promise<string>;
22
+ /** Opciones de seguridad ya resueltas. F-008 las tomará de `ress.config.ts`. */
23
+ security?: ResolvedSecurityOptions;
24
+ /** Opciones de seguridad sin resolver; se completan con los defaults. */
25
+ securityOptions?: SecurityOptions;
26
+ /**
27
+ * Configuración del proyecto ya resuelta. Si no se aporta, se carga de
28
+ * `ress.config` al arrancar: es el archivo que el proyecto ya declara, y
29
+ * exigir pasarla a mano deja al servidor y al build leyendo cosas distintas.
30
+ */
31
+ config?: ResolvedConfig;
32
+ /** Raíz del proyecto, para leer la configuración. */
33
+ root?: string;
34
+ /** Modo con el que se resuelven las capas de configuración. */
35
+ mode?: string;
14
36
  }
15
- export interface AutoViteServerOptions {
16
- port?: number;
17
- base?: string;
18
- enablePlatformDetection?: boolean;
19
- }
20
- export declare function createAutoViteServer(options?: AutoViteServerOptions): Promise<{
21
- app: import("express-serve-static-core").Express;
22
- vite: any;
23
- start: () => import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
24
- }>;
37
+ /**
38
+ * El router de ress.js.
39
+ *
40
+ * Antes había dos constructores con el 90 % del cuerpo repetido, uno para quien
41
+ * deja que el framework arme el runtime y otro para quien lo aporta. La
42
+ * diferencia real entre los dos es de dónde sale el runtime, así que eso es lo
43
+ * único que se decide acá; el resto del camino es uno solo.
44
+ */
25
45
  export declare function createViteRouter(options?: ViteRouterOptions): Router;
26
- export declare function listDetectedPages(): import("./pages").PageInfo[];
27
46
  export { getPages } from './pages';
package/dist/router.js CHANGED
@@ -1,161 +1,195 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.getPages = void 0;
7
- exports.createAutoViteServer = createAutoViteServer;
4
+ exports.waitForViteRouter = waitForViteRouter;
8
5
  exports.createViteRouter = createViteRouter;
9
- exports.listDetectedPages = listDetectedPages;
10
6
  const express_1 = require("express");
11
- const pages_1 = require("./pages");
12
- const express_2 = __importDefault(require("express"));
13
- const vite_config_1 = require("./helpers/vite-config");
14
- const middlewares_1 = require("./helpers/middlewares");
15
- const render_1 = require("./render");
16
- async function createAutoViteServer(options = {}) {
17
- const { port = 5173, base = '/', enablePlatformDetection = true } = options;
18
- const isProduction = process.env.NODE_ENV === 'production';
19
- const app = (0, express_2.default)();
20
- const viteConfig = await (0, vite_config_1.setupVite)(isProduction, base);
21
- if (!isProduction) {
22
- app.use(viteConfig.vite.middlewares);
23
- app.use('/dist/.entries', express_2.default.static('dist/.entries'));
24
- }
25
- else {
26
- const prodFactory = (0, vite_config_1.createProdMiddleware)();
27
- const prodMiddleware = await prodFactory();
28
- app.use(prodMiddleware);
29
- }
30
- const viteRouter = createViteRouter({
31
- enablePlatformDetection,
32
- vite: viteConfig.vite,
33
- templateHtml: viteConfig.templateHtml,
34
- isProduction
35
- });
36
- app.use('/', viteRouter);
37
- app.use(express_2.default.static('public'));
38
- const start = () => {
39
- return app.listen(port, () => {
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
- }
49
- });
50
- };
51
- return { app, vite: viteConfig.vite, start };
7
+ const prod_server_1 = require("./runtime/prod-server");
8
+ const security_1 = require("./security");
9
+ const router_1 = require("./routes/router");
10
+ const manifest_1 = require("./routes/manifest");
11
+ const scan_1 = require("./routes/scan");
12
+ const config_1 = require("./config");
13
+ const store_1 = require("./isr/store");
14
+ const request_handler_1 = require("./helpers/request-handler");
15
+ const routerReadiness = new WeakMap();
16
+ /** Espera a que el runtime, el manifest y las cadenas de middleware estén listos. */
17
+ function waitForViteRouter(router) {
18
+ return routerReadiness.get(router) ?? Promise.resolve();
52
19
  }
20
+ /**
21
+ * El router de ress.js.
22
+ *
23
+ * Antes había dos constructores con el 90 % del cuerpo repetido, uno para quien
24
+ * deja que el framework arme el runtime y otro para quien lo aporta. La
25
+ * diferencia real entre los dos es de dónde sale el runtime, así que eso es lo
26
+ * único que se decide acá; el resto del camino es uno solo.
27
+ */
53
28
  function createViteRouter(options = {}) {
54
- const needsAutoConfig = !options.vite && !options.templateHtml && options.isProduction !== true;
55
- if (needsAutoConfig) {
56
- return createAutoRouter(options);
57
- }
58
- return createManualRouter(options);
59
- }
60
- function createAutoRouter(options) {
61
29
  const { renderFunction, enablePlatformDetection = true } = options;
62
- const isProduction = process.env.NODE_ENV === 'production';
63
- let viteConfig = null;
64
- let initialized = false;
30
+ // Se resuelve recién dentro de `setup()`, después de cargar la
31
+ // configuración: `config.security` tiene que poder completar los defaults,
32
+ // y acá todavía no se leyó `ress.config`.
33
+ let security = options.security ?? (0, security_1.resolveSecurityOptions)(options.securityOptions);
34
+ const providesOwnRuntime = Boolean(options.moduleLoader || options.templateHtml);
35
+ const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
65
36
  const mainRouter = (0, express_1.Router)();
66
- const initializeIfNeeded = async () => {
67
- if (initialized)
68
- return viteConfig;
69
- const basePath = process.env.BASE || '/';
70
- viteConfig = await (0, vite_config_1.setupVite)(isProduction, basePath);
71
- if (!isProduction) {
72
- mainRouter.use(viteConfig.vite.middlewares);
37
+ let runtime = null;
38
+ /**
39
+ * El manifest vigente.
40
+ *
41
+ * Se guarda acá y no se lee del runtime en cada rearmado: en desarrollo hay
42
+ * que volver a escanear cuando aparece o desaparece un archivo, y el runtime
43
+ * lo construyó una sola vez al arrancar.
44
+ */
45
+ let routes = null;
46
+ let state = null;
47
+ let pageRouter = null;
48
+ let assetMiddleware = null;
49
+ let configMiddleware = null;
50
+ let stale = false;
51
+ let rescan = false;
52
+ /** La caché incremental: se crea con la configuración, una por router. */
53
+ let isr;
54
+ const markRescan = () => {
55
+ stale = true;
56
+ rescan = true;
57
+ isr?.store.clear();
58
+ };
59
+ const chainContext = () => ({
60
+ moduleLoader: runtime?.moduleLoader,
61
+ renderOptions: {
62
+ enablePlatformDetection,
63
+ renderFunction,
64
+ security,
65
+ viteConfig: () => runtime,
66
+ },
67
+ isr,
68
+ });
69
+ const rebuild = async (rescan = false) => {
70
+ stale = false;
71
+ if (rescan || !routes)
72
+ routes = (0, manifest_1.createRouteManifest)(await (0, scan_1.scanRoutes)());
73
+ state = await (0, router_1.buildRouterState)(routes, chainContext());
74
+ };
75
+ const setup = async () => {
76
+ // La configuración primero: de ella salen el prefijo de ruta, el prefijo de
77
+ // artefactos y las reglas del pipeline, y las tres se necesitan antes de
78
+ // armar el runtime.
79
+ const config = options.config ??
80
+ (await (0, config_1.loadConfig)(options.root ?? process.cwd(), options.mode ?? (isProduction ? 'production' : 'development')));
81
+ // `config.security` es el que declara el proyecto en `ress.config`; sólo
82
+ // se pisa si quien arma el router aportó algo explícito.
83
+ security = options.security ?? (0, security_1.resolveSecurityOptions)(options.securityOptions ?? config.security);
84
+ configMiddleware = (0, config_1.createConfigMiddleware)(config);
85
+ const assetPrefix = (0, config_1.assetPrefixFor)({
86
+ basePath: config.basePath,
87
+ cdnPrefix: config.assets?.cdnPrefix,
88
+ });
89
+ if (options.runtime) {
90
+ runtime = options.runtime;
91
+ routes = runtime.routes;
92
+ assetMiddleware = isProduction
93
+ ? await (0, prod_server_1.createProdAssetMiddleware)(undefined, { security, buildOutputs: runtime.buildOutputs })
94
+ : runtime.vite.middlewares;
73
95
  }
74
- else {
75
- const prodFactory = (0, vite_config_1.createProdMiddleware)();
76
- const prodMiddleware = await prodFactory();
77
- mainRouter.use(prodMiddleware);
96
+ else if (providesOwnRuntime) {
97
+ // Quien aporta su propio cargador de módulos no tiene runtime del
98
+ // framework, así que se arma uno mínimo con lo que declaró.
99
+ runtime = {
100
+ vite: null,
101
+ moduleLoader: options.moduleLoader,
102
+ templateHtml: options.templateHtml ?? '',
103
+ isProduction,
104
+ buildOutputs: new Set(),
105
+ close: async () => { },
106
+ };
78
107
  }
79
- initialized = true;
80
- return viteConfig;
81
- };
82
- const pages = (0, pages_1.getPages)();
83
- // Initialize Vite ONCE before setting up any routes
84
- const setupRoutes = async () => {
85
- if (!viteConfig)
86
- await initializeIfNeeded();
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, {
95
- enablePlatformDetection,
96
- renderFunction,
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));
108
+ else {
109
+ // El `base` de Vite se queda en `/`: el prefijo de ruta lo quita el
110
+ // pipeline de petición, y lo que se escribe en el HTML lo lleva por el
111
+ // prefijo de artefactos.
112
+ runtime = await (0, prod_server_1.createRuntime)(isProduction, options.basePath ?? process.env.BASE ?? '/', assetPrefix);
113
+ routes = runtime.routes;
114
+ // Los estáticos se atienden antes que las páginas: un pedido de artefacto
115
+ // no tiene que recorrer el manifest de rutas, y una ruta que capture todo
116
+ // no debe interceptarlos.
117
+ assetMiddleware = isProduction
118
+ ? await (0, prod_server_1.createProdAssetMiddleware)(undefined, { security, buildOutputs: runtime.buildOutputs })
119
+ : runtime.vite.middlewares;
110
120
  }
121
+ const isrOptions = config.isr ?? (0, config_1.resolveIsrConfig)();
122
+ isr = {
123
+ store: new store_1.MemoryIsrStore({
124
+ maxEntries: isrOptions.maxEntries,
125
+ maxBytes: isrOptions.maxBytes,
126
+ timeoutMs: isrOptions.generationTimeoutMs,
127
+ }),
128
+ options: isrOptions,
129
+ // En desarrollo la página se genera en cada petición, bajo el mismo
130
+ // contrato público: los datos se ven frescos y lo que funciona acá
131
+ // funciona igual en producción.
132
+ cacheEnabled: isProduction || isrOptions.dev,
133
+ buildId: runtime.buildId ?? 'dev',
134
+ usesNonce: security.csp.enabled,
135
+ isProduction,
136
+ // Una entrada ISR que no puede compartirse vuelve a este valor: nunca
137
+ // puede ser público, aunque `security.cache.html` lo sea.
138
+ htmlCacheControl: (0, security_1.privateCacheControl)(security.cache.html),
139
+ resolvePlatform: (req) => (0, request_handler_1.resolveRequestPlatform)(req, enablePlatformDetection),
140
+ };
141
+ await rebuild();
142
+ pageRouter = (0, router_1.createPageRouter)(() => state, chainContext());
143
+ // Se marca y se rearma en la petición siguiente, no acá: el watcher avisa
144
+ // antes de que Vite termine de invalidar su grafo, y rearmar en ese instante
145
+ // volvería a leer la versión anterior del archivo.
146
+ runtime.onSourceChange?.(() => {
147
+ stale = true;
148
+ isr?.store.clear();
149
+ });
150
+ // Crear o borrar una página cambia el conjunto de rutas, no sólo el
151
+ // contenido de un módulo: hay que volver a escanear.
152
+ runtime.vite?.watcher?.on('add', markRescan);
153
+ runtime.vite?.watcher?.on('unlink', markRescan);
111
154
  };
112
- // Setup routes asynchronously but don't block router creation
113
- setupRoutes().catch(console.error);
155
+ // El armado es asíncrono y una petición puede llegar antes de que termine. Se
156
+ // espera la promesa en vez de responder como si la ruta no existiera.
157
+ const ready = setup();
158
+ routerReadiness.set(mainRouter, ready);
159
+ mainRouter.use((req, res, next) => {
160
+ ready.then(() => {
161
+ const serve = () => {
162
+ // Se lee acá y no antes: la marca la pone el pipeline de configuración,
163
+ // que corre después de este callback.
164
+ const outside = req.ressOutsideBasePath === true;
165
+ // Después de la configuración, los artefactos: un pedido de artefacto no
166
+ // tiene que recorrer el manifest de rutas. Se registra acá y no con
167
+ // `use()` porque los dos middlewares se resuelven dentro del armado
168
+ // asíncrono, y registrarlos después dejaría al despachador por delante.
169
+ // Fuera del prefijo no hay páginas: declarar un prefijo y seguir
170
+ // sirviendo el sitio entero por la raíz es exactamente lo que un prefijo
171
+ // existe para evitar. Los artefactos sí se intentan igual, porque las
172
+ // rutas internas del servidor de desarrollo de Vite —`/@vite/client`,
173
+ // `/@fs/`— viven en la raíz y no bajo el prefijo de la aplicación.
174
+ const after = (err) => err ? next(err) : outside ? next() : dispatch(req, res, next);
175
+ if (!assetMiddleware)
176
+ return after();
177
+ assetMiddleware(req, res, after);
178
+ };
179
+ // Barra final, prefijo de ruta, redirecciones y reescrituras: todo lo que
180
+ // decide *qué* URL se está atendiendo va antes que quien la atiende.
181
+ if (!configMiddleware)
182
+ return serve();
183
+ configMiddleware(req, res, ((err) => (err ? next(err) : serve())));
184
+ }, next);
185
+ });
186
+ function dispatch(req, res, next) {
187
+ const run = () => (pageRouter ? pageRouter(req, res, next) : next());
188
+ if (!stale)
189
+ return run();
190
+ rebuild(rescan).then(() => { rescan = false; run(); }, next);
191
+ }
114
192
  return mainRouter;
115
193
  }
116
- function createManualRouter(options) {
117
- const { vite, renderFunction, enablePlatformDetection = true } = options;
118
- const router = (0, express_1.Router)();
119
- const pages = (0, pages_1.getPages)();
120
- // Crear viteConfig para processRequest
121
- const viteConfig = {
122
- vite,
123
- templateHtml: options.templateHtml || '',
124
- isProduction: options.isProduction || false,
125
- manifest: null
126
- };
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, {
136
- enablePlatformDetection,
137
- renderFunction,
138
- viteConfig
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);
155
- return router;
156
- }
157
- function listDetectedPages() {
158
- return (0, pages_1.getPages)();
159
- }
160
- var pages_2 = require("./pages");
161
- Object.defineProperty(exports, "getPages", { enumerable: true, get: function () { return pages_2.getPages; } });
194
+ var pages_1 = require("./pages");
195
+ Object.defineProperty(exports, "getPages", { enumerable: true, get: function () { return pages_1.getPages; } });
@@ -0,0 +1,58 @@
1
+ import type { NextFunction, Request, RequestHandler, Response } from 'express';
2
+ import type { RequestPlatform } from '../isr/key';
3
+ import type { ResolvedPageRendering } from '../isr/types';
4
+ import type { RouteEntry, RouteManifest } from './types';
5
+ declare global {
6
+ namespace Express {
7
+ interface Request {
8
+ /** La ruta que atendió esta petición. La usa el manejador de errores. */
9
+ ressRoute?: RouteEntry;
10
+ /**
11
+ * Los parámetros de la URL.
12
+ *
13
+ * Express reasigna `req.params` al entrar a cada router anidado, así que
14
+ * los valores que el despachador extrajo se perderían en el camino. Acá
15
+ * quedan en un campo propio, y `req.params` se mantiene por compatibilidad
16
+ * con quien ya lo lee.
17
+ */
18
+ ressParams?: Record<string, string | string[]>;
19
+ /**
20
+ * La plataforma ya resuelta. La deja la caché incremental: la plataforma
21
+ * de la clave y la del documento tienen que ser la misma por construcción.
22
+ */
23
+ ressPlatform?: RequestPlatform;
24
+ /**
25
+ * La configuración de render efectiva de esta petición: el export
26
+ * estático combinado con lo que devolvió `getServerSideProps`. Sólo
27
+ * existe si `getServerSideProps` devolvió campos de render.
28
+ */
29
+ ressPageRendering?: ResolvedPageRendering;
30
+ }
31
+ }
32
+ }
33
+ /** Cómo se arma la cadena de middlewares de una entrada. */
34
+ export type ChainBuilder = (entry: RouteEntry) => Promise<RequestHandler[]>;
35
+ /**
36
+ * Un único punto de entrada para todas las rutas.
37
+ *
38
+ * El modelo anterior registraba una ruta de Express por página, lo que obligaba
39
+ * a traducir cada patrón a la sintaxis de Express —que no puede expresar un
40
+ * catch-all opcional ni un grupo— y dejaba la precedencia en manos del orden de
41
+ * registro. Acá el manifest decide, y Express sólo ve un middleware.
42
+ *
43
+ * Las cadenas se construyen una vez, no por petición. `chains` se reemplaza
44
+ * entero cuando el manifest cambia en desarrollo, de modo que una petición nunca
45
+ * ve una mezcla de dos versiones.
46
+ */
47
+ export declare function createDispatcher(getState: () => {
48
+ manifest: RouteManifest;
49
+ chains: Map<string, RequestHandler[]>;
50
+ }): RequestHandler;
51
+ /**
52
+ * Ejecuta la cadena respetando el contrato de Express.
53
+ *
54
+ * Cada middleware decide si sigue: `next()` avanza, `next(error)` corta y
55
+ * propaga. Un throw síncrono se convierte en `next(error)` para que no quede una
56
+ * petición colgada.
57
+ */
58
+ export declare function runMiddlewareChain(chain: readonly RequestHandler[], req: Request, res: Response, done: NextFunction): void;