@ressjs/vite-router 0.5.0 → 0.6.0-experimental.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 (110) hide show
  1. package/README.md +41 -31
  2. package/dist/client/entry.d.mts +2 -0
  3. package/dist/client/entry.mjs +1 -0
  4. package/dist/client/index.d.ts +2 -0
  5. package/dist/client/index.js +8 -0
  6. package/dist/config/base-path.d.ts +44 -0
  7. package/dist/config/base-path.js +100 -0
  8. package/dist/config/codegen.d.ts +23 -0
  9. package/dist/config/codegen.js +118 -0
  10. package/dist/config/define.d.ts +8 -0
  11. package/dist/config/define.js +12 -0
  12. package/dist/config/env.d.ts +19 -0
  13. package/dist/config/env.js +40 -0
  14. package/dist/config/index.d.ts +18 -0
  15. package/dist/config/index.js +44 -0
  16. package/dist/config/load.d.ts +26 -0
  17. package/dist/config/load.js +73 -0
  18. package/dist/config/middleware.d.ts +18 -0
  19. package/dist/config/middleware.js +79 -0
  20. package/dist/config/rules.d.ts +48 -0
  21. package/dist/config/rules.js +149 -0
  22. package/dist/config/types.d.ts +164 -0
  23. package/dist/config/types.js +2 -0
  24. package/dist/config/validate.d.ts +19 -0
  25. package/dist/config/validate.js +105 -0
  26. package/dist/config/watch.d.ts +41 -0
  27. package/dist/config/watch.js +132 -0
  28. package/dist/fs/module-extensions.d.ts +82 -0
  29. package/dist/fs/module-extensions.js +196 -0
  30. package/dist/helpers/html-generator.d.ts +22 -5
  31. package/dist/helpers/html-generator.js +89 -120
  32. package/dist/helpers/middlewares.d.ts +7 -7
  33. package/dist/helpers/middlewares.js +51 -103
  34. package/dist/helpers/request-handler.d.ts +2 -2
  35. package/dist/helpers/request-handler.js +118 -25
  36. package/dist/index.d.ts +30 -3
  37. package/dist/index.js +70 -8
  38. package/dist/pages.d.ts +10 -9
  39. package/dist/pages.js +42 -261
  40. package/dist/platform.d.ts +11 -14
  41. package/dist/platform.js +18 -102
  42. package/dist/plugin/environments.d.ts +66 -0
  43. package/dist/plugin/environments.js +72 -0
  44. package/dist/plugin/index.d.ts +54 -0
  45. package/dist/plugin/index.js +178 -0
  46. package/dist/plugin/virtual-entries.d.ts +26 -0
  47. package/dist/plugin/virtual-entries.js +80 -0
  48. package/dist/render.d.ts +23 -2
  49. package/dist/render.js +40 -53
  50. package/dist/router.d.ts +32 -13
  51. package/dist/router.js +147 -146
  52. package/dist/routes/dispatcher.d.ts +37 -0
  53. package/dist/routes/dispatcher.js +69 -0
  54. package/dist/routes/manifest.d.ts +17 -0
  55. package/dist/routes/manifest.js +56 -0
  56. package/dist/routes/match.d.ts +20 -0
  57. package/dist/routes/match.js +75 -0
  58. package/dist/routes/parse.d.ts +31 -0
  59. package/dist/routes/parse.js +114 -0
  60. package/dist/routes/rank.d.ts +12 -0
  61. package/dist/routes/rank.js +49 -0
  62. package/dist/routes/router.d.ts +44 -0
  63. package/dist/routes/router.js +81 -0
  64. package/dist/routes/scan.d.ts +16 -0
  65. package/dist/routes/scan.js +82 -0
  66. package/dist/routes/types.d.ts +85 -0
  67. package/dist/routes/types.js +13 -0
  68. package/dist/runtime/dev-server.d.ts +62 -0
  69. package/dist/runtime/dev-server.js +94 -0
  70. package/dist/runtime/module-loader.d.ts +55 -0
  71. package/dist/runtime/module-loader.js +122 -0
  72. package/dist/runtime/prod-server.d.ts +42 -0
  73. package/dist/runtime/prod-server.js +142 -0
  74. package/dist/runtime/template.d.ts +10 -0
  75. package/dist/runtime/template.js +33 -0
  76. package/dist/security/client-props.d.ts +25 -0
  77. package/dist/security/client-props.js +50 -0
  78. package/dist/security/config.d.ts +130 -0
  79. package/dist/security/config.js +73 -0
  80. package/dist/security/csp.d.ts +53 -0
  81. package/dist/security/csp.js +118 -0
  82. package/dist/security/dev-hardening.d.ts +46 -0
  83. package/dist/security/dev-hardening.js +65 -0
  84. package/dist/security/errors.d.ts +37 -0
  85. package/dist/security/errors.js +84 -0
  86. package/dist/security/escape.d.ts +40 -0
  87. package/dist/security/escape.js +90 -0
  88. package/dist/security/head-tags.d.ts +32 -0
  89. package/dist/security/head-tags.js +111 -0
  90. package/dist/security/headers.d.ts +62 -0
  91. package/dist/security/headers.js +187 -0
  92. package/dist/security/index.d.ts +24 -0
  93. package/dist/security/index.js +47 -0
  94. package/dist/security/serialize.d.ts +48 -0
  95. package/dist/security/serialize.js +146 -0
  96. package/dist/variants/assets.d.ts +23 -0
  97. package/dist/variants/assets.js +78 -0
  98. package/dist/variants/catalog.d.ts +33 -0
  99. package/dist/variants/catalog.js +79 -0
  100. package/dist/variants/platform-tokens.d.ts +9 -0
  101. package/dist/variants/platform-tokens.js +15 -0
  102. package/dist/variants/resolve.d.ts +40 -0
  103. package/dist/variants/resolve.js +90 -0
  104. package/dist/variants/suffix.d.ts +8 -0
  105. package/dist/variants/suffix.js +12 -0
  106. package/dist/variants/types.d.ts +69 -0
  107. package/dist/variants/types.js +2 -0
  108. package/package.json +22 -10
  109. package/dist/helpers/vite-config.d.ts +0 -10
  110. package/dist/helpers/vite-config.js +0 -90
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Plugin de ress.js para Vite.
3
+ *
4
+ * Único punto de entrada de configuración para un proyecto:
5
+ *
6
+ * ```ts
7
+ * import { defineConfig } from 'vite'
8
+ * import { ressPlugin } from '@ressjs/vite-router/plugin'
9
+ *
10
+ * export default defineConfig({ plugins: [ressPlugin()] })
11
+ * ```
12
+ */
13
+ import { defineEnvironments, OUT_DIR } from './environments';
14
+ import type { VariantCatalog } from '../variants/types';
15
+ import { type ExtensionPolicy } from '../fs/module-extensions';
16
+ import { type SecurityOptions } from '../security';
17
+ /**
18
+ * El tipo `Plugin` de Vite se declara estructuralmente en vez de importarse,
19
+ * para que este paquete no dependa del tipo en tiempo de compilación. Vite es
20
+ * una dependencia entre pares: el consumidor la aporta.
21
+ */
22
+ export interface RessPlugin {
23
+ name: string;
24
+ /** Opcional: el guard de `@ressjs/config` sólo aporta `resolveId`. */
25
+ config?: (userConfig: unknown, env: {
26
+ command: string;
27
+ mode: string;
28
+ }) => Promise<unknown> | unknown;
29
+ resolveId?: (id: string, importer?: string, options?: unknown) => string | undefined | null;
30
+ load?: (id: string) => string | undefined;
31
+ configureServer?: (server: any) => void;
32
+ writeBundle?: () => Promise<void> | void;
33
+ }
34
+ export declare function getVariantCatalog(): VariantCatalog | undefined;
35
+ export declare function refreshVariantCatalog(): Promise<VariantCatalog>;
36
+ export interface RessPluginOptions {
37
+ security?: SecurityOptions;
38
+ /**
39
+ * Qué extensiones acepta el proyecto. Se fusiona sobre los defaults una vez al
40
+ * arrancar. F-008 la absorbe cuando exista el archivo de configuración.
41
+ */
42
+ extensions?: Partial<ExtensionPolicy>;
43
+ }
44
+ /**
45
+ * El plugin del framework.
46
+ *
47
+ * Devuelve dos: el propio y el guard que impide que `@ressjs/config` —valores de
48
+ * servidor, credenciales incluidas— termine en el paquete del navegador. El
49
+ * segundo no se agrega a mano justamente porque olvidarse es el modo de fallo que
50
+ * existe para evitar.
51
+ */
52
+ export declare function ressPlugin(options?: RessPluginOptions): RessPlugin[];
53
+ export declare function ressCorePlugin(options?: RessPluginOptions): RessPlugin;
54
+ export { defineEnvironments, OUT_DIR };
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ /**
3
+ * Plugin de ress.js para Vite.
4
+ *
5
+ * Único punto de entrada de configuración para un proyecto:
6
+ *
7
+ * ```ts
8
+ * import { defineConfig } from 'vite'
9
+ * import { ressPlugin } from '@ressjs/vite-router/plugin'
10
+ *
11
+ * export default defineConfig({ plugins: [ressPlugin()] })
12
+ * ```
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.OUT_DIR = exports.defineEnvironments = void 0;
16
+ exports.getVariantCatalog = getVariantCatalog;
17
+ exports.refreshVariantCatalog = refreshVariantCatalog;
18
+ exports.ressPlugin = ressPlugin;
19
+ exports.ressCorePlugin = ressCorePlugin;
20
+ const environments_1 = require("./environments");
21
+ Object.defineProperty(exports, "defineEnvironments", { enumerable: true, get: function () { return environments_1.defineEnvironments; } });
22
+ Object.defineProperty(exports, "OUT_DIR", { enumerable: true, get: function () { return environments_1.OUT_DIR; } });
23
+ const pages_1 = require("../pages");
24
+ const catalog_1 = require("../variants/catalog");
25
+ const virtual_entries_1 = require("./virtual-entries");
26
+ const scan_1 = require("../routes/scan");
27
+ const module_extensions_1 = require("../fs/module-extensions");
28
+ const manifest_1 = require("../routes/manifest");
29
+ const security_1 = require("../security");
30
+ const config_1 = require("@ressjs/config");
31
+ const config_2 = require("../config");
32
+ /**
33
+ * Catálogo vigente del proceso.
34
+ *
35
+ * Se arma una vez y lo comparten el build y el servidor: es lo que garantiza que
36
+ * las entradas que se construyen y las que se resuelven por petición salgan de la
37
+ * misma fuente.
38
+ */
39
+ let current;
40
+ function getVariantCatalog() {
41
+ return current;
42
+ }
43
+ async function refreshVariantCatalog() {
44
+ current = await (0, catalog_1.buildVariantCatalog)({ pages: (0, pages_1.getPages)() });
45
+ return current;
46
+ }
47
+ async function resolveInputs() {
48
+ const catalog = await refreshVariantCatalog();
49
+ return {
50
+ client: (0, virtual_entries_1.clientBuildInputs)(catalog),
51
+ ssr: (0, pages_1.getViteSSRInput)(),
52
+ };
53
+ }
54
+ /**
55
+ * El plugin del framework.
56
+ *
57
+ * Devuelve dos: el propio y el guard que impide que `@ressjs/config` —valores de
58
+ * servidor, credenciales incluidas— termine en el paquete del navegador. El
59
+ * segundo no se agrega a mano justamente porque olvidarse es el modo de fallo que
60
+ * existe para evitar.
61
+ */
62
+ function ressPlugin(options = {}) {
63
+ return [ressCorePlugin(options), (0, config_1.configServerOnlyGuard)()];
64
+ }
65
+ function ressCorePlugin(options = {}) {
66
+ const security = (0, security_1.resolveSecurityOptions)(options.security);
67
+ /** Raíz del proyecto según Vite; puede no ser el directorio de trabajo. */
68
+ let root = process.cwd();
69
+ let manifestWritten = false;
70
+ let extensions = { ...module_extensions_1.DEFAULT_EXTENSION_POLICY, ...options.extensions };
71
+ /** La configuración del proyecto, vigente desde el hook `config`. */
72
+ let config;
73
+ return {
74
+ name: 'ress',
75
+ /**
76
+ * Se aporta desde `config` y no desde `configResolved` para que la
77
+ * configuración del usuario se fusione encima siguiendo las reglas normales
78
+ * de Vite, en vez de ser sobrescrita por el framework.
79
+ */
80
+ async config(userConfig, env) {
81
+ // Se verifica acá, con la configuración del proyecto ya visible y antes de
82
+ // que el servidor escuche. Un aviso en la consola de un servidor de
83
+ // desarrollo no lo lee nadie; un error de arranque sí.
84
+ root = userConfig?.root ?? process.cwd();
85
+ // La configuración del proyecto se lee antes que nada: declara qué
86
+ // extensiones son código de usuario, bajo qué prefijo se sirve todo y qué
87
+ // variables llegan al navegador, y las tres decisiones condicionan lo que
88
+ // este hook devuelve.
89
+ config = await (0, config_2.loadConfig)(root, env?.mode ?? 'development');
90
+ extensions = { ...extensions, ...config.extensions };
91
+ await (0, config_2.writeGeneratedTypes)(root, {
92
+ 'ress-platform.d.ts': (0, config_2.generatePlatformTypes)(config.platform),
93
+ 'ress-config.d.ts': (0, config_2.generateProjectConfigTypes)((await import('@ressjs/config')).resolved()),
94
+ });
95
+ // Se fija acá y no al construir el plugin: `current` es estado global del
96
+ // proceso, así que crear un objeto de plugin —dos configuraciones en un
97
+ // mismo proceso, un test que lo importa— reescribiría la política que leen
98
+ // el descubrimiento y el escaneo de rutas.
99
+ (0, module_extensions_1.setExtensionPolicy)(extensions);
100
+ if (env?.command === 'serve')
101
+ (0, security_1.assertDevServerHardening)(userConfig);
102
+ return {
103
+ ...(0, environments_1.defineEnvironments)(await resolveInputs()),
104
+ // Sólo al construir: en desarrollo el prefijo lo quita el pipeline de
105
+ // petición antes de que Vite vea la URL, así que su `base` sigue siendo
106
+ // `/`. Al construir sí tiene que quedar escrito en las referencias que
107
+ // los artefactos hacen entre sí.
108
+ ...(config.basePath && env?.command === 'build'
109
+ ? { base: config.basePath + '/' }
110
+ : {}),
111
+ // Sólo lo declarado en `publicEnv`. Lo demás no queda como `undefined`
112
+ // en el navegador: queda como código que nunca se generó.
113
+ ...(config.publicEnv.length
114
+ ? { define: (0, config_2.publicEnvDefines)(config.publicEnv, { strict: env?.command === 'build' }) }
115
+ : {}),
116
+ ...(env?.command === 'serve'
117
+ ? { server: (0, security_1.devServerDefaults)(security.dev.allowedOrigins) }
118
+ : {}),
119
+ };
120
+ },
121
+ /**
122
+ * Emite el manifest de rutas junto al build.
123
+ *
124
+ * Cargarlo en producción es lo que evita volver a recorrer el sistema de
125
+ * archivos en cada arranque para deducir algo que ya no puede cambiar.
126
+ *
127
+ * El hook corre una vez por entorno —cliente y servidor— y el manifest es
128
+ * el mismo para los dos, así que se escribe una sola vez.
129
+ */
130
+ async writeBundle() {
131
+ if (manifestWritten)
132
+ return;
133
+ manifestWritten = true;
134
+ const { writeFile, mkdir } = await import('node:fs/promises');
135
+ const path = await import('node:path');
136
+ const data = await (0, scan_1.scanRoutes)({ strict: true, pagesDir: path.join(root, 'app/pages') });
137
+ const target = path.resolve(root, environments_1.OUT_DIR.base);
138
+ await mkdir(target, { recursive: true });
139
+ await writeFile(path.join(target, 'route-manifest.json'), (0, manifest_1.serializeRouteManifest)(data), 'utf-8');
140
+ },
141
+ resolveId(id) {
142
+ return (0, virtual_entries_1.resolveEntryId)(id);
143
+ },
144
+ load(id) {
145
+ const parsed = (0, virtual_entries_1.parseEntryId)(id);
146
+ if (!parsed || !current)
147
+ return undefined;
148
+ return (0, virtual_entries_1.generateEntryModule)(current, parsed.pageId, parsed.suffix);
149
+ },
150
+ /**
151
+ * Rearma el catálogo cuando cambia el **conjunto** de archivos, no su
152
+ * contenido: agregar o borrar una hoja de estilos cambia qué variantes
153
+ * declara una página, y eso tiene que reflejarse sin reiniciar. Los cambios
154
+ * de contenido los maneja la recarga en caliente de Vite.
155
+ */
156
+ configureServer(server) {
157
+ const declaresVariants = (file) => file.includes('app/pages/') || file.includes('app\\pages\\');
158
+ const rebuild = (file) => {
159
+ if (declaresVariants(file))
160
+ void refreshVariantCatalog();
161
+ };
162
+ server.watcher?.on('add', rebuild);
163
+ server.watcher?.on('unlink', rebuild);
164
+ // Las dos superficies de configuración. Observar sólo `ress.config` dejaba
165
+ // mudo el cambio de una capa de `config/`.
166
+ if (config) {
167
+ (0, config_2.watchConfig)(server, {
168
+ root,
169
+ mode: server.config?.mode ?? 'development',
170
+ current: () => config,
171
+ onReload: (next) => {
172
+ config = next;
173
+ },
174
+ });
175
+ }
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,26 @@
1
+ import type { VariantCatalog } from '../variants/types';
2
+ declare const PREFIX = "ress:entry";
3
+ declare const RESOLVED: string;
4
+ /** ¿Es el identificador de una entrada de build del catálogo? */
5
+ export declare function isEntryId(id: string): boolean;
6
+ export declare function resolveEntryId(id: string): string | undefined;
7
+ /** Extrae página y variante del identificador. */
8
+ export declare function parseEntryId(id: string): {
9
+ pageId: string;
10
+ suffix: string;
11
+ } | undefined;
12
+ /**
13
+ * Genera el módulo de hidratación de una página en una variante.
14
+ *
15
+ * Se escribe con `createElement` y no con JSX a propósito: así el módulo no
16
+ * depende de la transformación JSX ni de una extensión concreta, que es lo que
17
+ * permite que una página escrita en JavaScript se hidrate con el mismo código
18
+ * que una escrita en TypeScript.
19
+ */
20
+ export declare function generateEntryModule(catalog: VariantCatalog, pageId: string, variantSuffix: string): string;
21
+ /**
22
+ * Qué le pasa el framework al empaquetador como entradas del entorno de cliente:
23
+ * una por página y variante, cada una apuntando a su módulo virtual.
24
+ */
25
+ export declare function clientBuildInputs(catalog: VariantCatalog): Record<string, string>;
26
+ export { PREFIX as ENTRY_PREFIX, RESOLVED as ENTRY_RESOLVED_PREFIX };
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ENTRY_RESOLVED_PREFIX = exports.ENTRY_PREFIX = void 0;
7
+ exports.isEntryId = isEntryId;
8
+ exports.resolveEntryId = resolveEntryId;
9
+ exports.parseEntryId = parseEntryId;
10
+ exports.generateEntryModule = generateEntryModule;
11
+ exports.clientBuildInputs = clientBuildInputs;
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const catalog_1 = require("../variants/catalog");
14
+ const PREFIX = 'ress:entry';
15
+ exports.ENTRY_PREFIX = PREFIX;
16
+ const RESOLVED = '\0' + PREFIX;
17
+ exports.ENTRY_RESOLVED_PREFIX = RESOLVED;
18
+ /** ¿Es el identificador de una entrada de build del catálogo? */
19
+ function isEntryId(id) {
20
+ return id.startsWith(PREFIX) || id.startsWith(RESOLVED);
21
+ }
22
+ function resolveEntryId(id) {
23
+ return id.startsWith(PREFIX) ? '\0' + id : undefined;
24
+ }
25
+ /** Extrae página y variante del identificador. */
26
+ function parseEntryId(id) {
27
+ if (!id.startsWith(RESOLVED))
28
+ return undefined;
29
+ const query = id.slice(RESOLVED.length + 1); // salta el '?'
30
+ const params = new URLSearchParams(query);
31
+ const pageId = params.get('page');
32
+ if (!pageId)
33
+ return undefined;
34
+ return { pageId, suffix: params.get('variant') ?? '' };
35
+ }
36
+ /**
37
+ * Genera el módulo de hidratación de una página en una variante.
38
+ *
39
+ * Se escribe con `createElement` y no con JSX a propósito: así el módulo no
40
+ * depende de la transformación JSX ni de una extensión concreta, que es lo que
41
+ * permite que una página escrita en JavaScript se hidrate con el mismo código
42
+ * que una escrita en TypeScript.
43
+ */
44
+ function generateEntryModule(catalog, pageId, variantSuffix) {
45
+ const page = catalog.pagesById.get(pageId);
46
+ if (!page) {
47
+ throw new Error(`[ress] No hay ninguna página con el identificador "${pageId}".`);
48
+ }
49
+ const variant = catalog.variantsByPage
50
+ .get(pageId)
51
+ ?.find((v) => v.suffix === variantSuffix);
52
+ if (!variant) {
53
+ throw new Error(`[ress] La página "${pageId}" no declara la variante "${variantSuffix || '(base)'}".`);
54
+ }
55
+ const pageAbs = node_path_1.default.resolve(page.pageFile).replace(/\\/g, '/');
56
+ const styleImport = variant.styleFile
57
+ ? `import ${JSON.stringify(node_path_1.default.resolve(variant.styleFile).replace(/\\/g, '/'))};\n`
58
+ : '';
59
+ return `${styleImport}import { hydrateRoot } from "react-dom/client";
60
+ import { StrictMode, createElement } from "react";
61
+ import Page from ${JSON.stringify(pageAbs)};
62
+
63
+ const props = window.__RESS_PROPS__ || window.__INITIAL_PROPS__ || {};
64
+ const root = document.getElementById("root");
65
+
66
+ if (root) {
67
+ hydrateRoot(root, createElement(StrictMode, null, createElement(Page, props)));
68
+ }
69
+ `;
70
+ }
71
+ /**
72
+ * Qué le pasa el framework al empaquetador como entradas del entorno de cliente:
73
+ * una por página y variante, cada una apuntando a su módulo virtual.
74
+ */
75
+ function clientBuildInputs(catalog) {
76
+ return Object.fromEntries(catalog.buildEntries.map((entry) => [
77
+ (0, catalog_1.buildEntryNameFor)(entry.pageId, entry.variant.suffix),
78
+ entry.virtualId,
79
+ ]));
80
+ }
package/dist/render.d.ts CHANGED
@@ -1,18 +1,39 @@
1
1
  import { RequestHandler } from 'express';
2
+ import type { ModuleLoader } from './runtime/module-loader';
3
+ import type { ClientPropsPolicy } from './security';
2
4
  /**
3
5
  * RENDER MIDDLEWARE PARA EL ROUTER
4
6
  */
5
7
  export declare function createRenderMiddleware(page: any, options: any): RequestHandler;
6
8
  export interface RenderOptions {
7
9
  url: string;
10
+ /**
11
+ * La página que el despachador ya resolvió.
12
+ *
13
+ * Sin esto el render vuelve a buscarla por URL, con un criterio propio que no
14
+ * conoce grupos ni catch-all: dos resoluciones distintas para la misma
15
+ * pregunta, que es el defecto que esta spec elimina.
16
+ */
17
+ page?: {
18
+ route: string;
19
+ file: string;
20
+ pageId?: string;
21
+ abs?: string;
22
+ };
8
23
  props?: any;
24
+ /** Estado que dejaron los middlewares. Sólo viaja al cliente lo que la política declara. */
25
+ locals?: Record<string, unknown>;
9
26
  }
10
27
  export interface CreateRenderServerOptions {
11
- vite?: any;
28
+ /** Cargador de módulos. Ausente en producción: se importa el artefacto compilado. */
29
+ moduleLoader?: ModuleLoader;
30
+ /** Qué claves del estado del servidor viajan al cliente. */
31
+ clientProps?: ClientPropsPolicy;
32
+ isProduction?: boolean;
12
33
  }
13
34
  export declare function createRenderServer(options?: CreateRenderServerOptions): (renderOptions: RenderOptions) => Promise<{
14
35
  html: string;
15
- props: any;
36
+ props: Record<string, unknown>;
16
37
  route: string;
17
38
  page: string;
18
39
  }>;
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
  };
@@ -42,6 +9,8 @@ const react_1 = __importDefault(require("react"));
42
9
  const server_1 = require("react-dom/server");
43
10
  const pages_1 = require("./pages");
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");
45
14
  /**
46
15
  * RENDER MIDDLEWARE PARA EL ROUTER
47
16
  */
@@ -57,9 +26,11 @@ function createRenderMiddleware(page, options) {
57
26
  }
58
27
  function createRenderServer(options = {}) {
59
28
  const pages = (0, pages_1.getPages)();
60
- const { vite } = options;
29
+ const { moduleLoader } = options;
30
+ const policy = options.clientProps ?? (0, security_1.resolveSecurityOptions)().clientProps;
31
+ const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
61
32
  return async (renderOptions) => {
62
- const { url, props = {} } = renderOptions;
33
+ const { url, props = {}, locals = {}, page } = renderOptions;
63
34
  // Encuentra la página que coincide con la URL
64
35
  // Función helper para hacer matching de rutas dinámicas
65
36
  const matchRoute = (pageRoute, url) => {
@@ -77,28 +48,18 @@ function createRenderServer(options = {}) {
77
48
  const regex = new RegExp(`^${regexPattern}$`);
78
49
  return regex.test(url);
79
50
  };
80
- const matchedPage = pages.find(page => matchRoute(page.route, url));
51
+ const matchedPage = page ?? pages.find(p => matchRoute(p.route, url));
81
52
  if (!matchedPage) {
82
53
  throw new Error(`Page not found for URL: ${url}`);
83
54
  }
84
55
  try {
85
- // Import dinámico de la página usando vite.ssrLoadModule si está disponible
86
56
  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);
57
+ if (moduleLoader) {
58
+ pageModule = await moduleLoader.load(`/${matchedPage.file}`);
91
59
  }
92
60
  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
- }
61
+ const moduleUrl = (0, prod_server_1.resolveServerModulePath)(matchedPage.abs ?? matchedPage.file);
62
+ pageModule = await import(/* @vite-ignore */ moduleUrl);
102
63
  }
103
64
  const PageComponent = pageModule.default;
104
65
  if (!PageComponent) {
@@ -113,9 +74,17 @@ function createRenderServer(options = {}) {
113
74
  };
114
75
  // Renderiza el componente con las props finales
115
76
  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;
77
+ // El componente ya recibió todo arriba. Lo que viaja al cliente es sólo
78
+ // lo que la política declara: enumerar lo que se excluye deja expuesta
79
+ // cada clave que un middleware agregue después.
80
+ //
81
+ // `serverSideProps` es lo que la página pidió explícitamente para el
82
+ // cliente; el resto de `res.locals` es estado del servidor y no viaja
83
+ // salvo que se declare.
84
+ const exposable = { ...locals, serverSideProps: props };
85
+ const clientProps = (0, security_1.pickClientProps)(exposable, policy);
86
+ if (!isProduction)
87
+ warnWithheld(matchedPage.route, exposable, clientProps);
119
88
  return {
120
89
  html,
121
90
  props: clientProps,
@@ -129,3 +98,21 @@ function createRenderServer(options = {}) {
129
98
  }
130
99
  };
131
100
  }
101
+ /**
102
+ * Avisa, una vez por ruta, qué quedó fuera del cliente.
103
+ *
104
+ * Una página que renderiza en el servidor con datos que el cliente no tiene
105
+ * hidrata distinto, y ese desajuste no dice de dónde viene. El aviso lo nombra.
106
+ */
107
+ const warnedRoutes = new Set();
108
+ function warnWithheld(route, serverProps, clientProps) {
109
+ if (warnedRoutes.has(route))
110
+ return;
111
+ const withheld = (0, security_1.withheldKeys)(serverProps, clientProps);
112
+ if (withheld.length === 0)
113
+ return;
114
+ warnedRoutes.add(route);
115
+ console.warn(`[ress] ${route}: estas claves existen en el servidor y no viajan al cliente: ` +
116
+ `${withheld.join(', ')}.\n` +
117
+ 'Si la página las usa al hidratar, declaralas en `security.clientProps.expose`.');
118
+ }
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';