@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
@@ -0,0 +1,59 @@
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 BuildOutputBundle } from './route-manifest';
17
+ import { type SecurityOptions } from '../security';
18
+ /**
19
+ * El tipo `Plugin` de Vite se declara estructuralmente en vez de importarse,
20
+ * para que este paquete no dependa del tipo en tiempo de compilación. Vite es
21
+ * una dependencia entre pares: el consumidor la aporta.
22
+ */
23
+ export interface RessPlugin {
24
+ name: string;
25
+ /** Opcional: el guard de `@ressjs/config` sólo aporta `resolveId`. */
26
+ config?: (userConfig: unknown, env: {
27
+ command: string;
28
+ mode: string;
29
+ }) => Promise<unknown> | unknown;
30
+ resolveId?: (id: string, importer?: string, options?: unknown) => string | undefined | null;
31
+ load?: (id: string) => string | undefined;
32
+ configureServer?: (server: any) => void;
33
+ writeBundle?: (this: {
34
+ environment?: {
35
+ name?: string;
36
+ };
37
+ }, options: unknown, bundle: BuildOutputBundle) => Promise<void> | void;
38
+ }
39
+ export declare function getVariantCatalog(): VariantCatalog | undefined;
40
+ export declare function refreshVariantCatalog(): Promise<VariantCatalog>;
41
+ export interface RessPluginOptions {
42
+ security?: SecurityOptions;
43
+ /**
44
+ * Qué extensiones acepta el proyecto. Se fusiona sobre los defaults una vez al
45
+ * arrancar. F-008 la absorbe cuando exista el archivo de configuración.
46
+ */
47
+ extensions?: Partial<ExtensionPolicy>;
48
+ }
49
+ /**
50
+ * El plugin del framework.
51
+ *
52
+ * Devuelve dos: el propio y el guard que impide que `@ressjs/config` —valores de
53
+ * servidor, credenciales incluidas— termine en el paquete del navegador. El
54
+ * segundo no se agrega a mano justamente porque olvidarse es el modo de fallo que
55
+ * existe para evitar.
56
+ */
57
+ export declare function ressPlugin(options?: RessPluginOptions): RessPlugin[];
58
+ export declare function ressCorePlugin(options?: RessPluginOptions): RessPlugin;
59
+ export { defineEnvironments, OUT_DIR };
@@ -0,0 +1,195 @@
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 middlewares_1 = require("../helpers/middlewares");
28
+ const module_extensions_1 = require("../fs/module-extensions");
29
+ const manifest_1 = require("../routes/manifest");
30
+ const route_manifest_1 = require("./route-manifest");
31
+ const security_1 = require("../security");
32
+ const config_1 = require("@ressjs/config");
33
+ const config_2 = require("../config");
34
+ /**
35
+ * Catálogo vigente del proceso.
36
+ *
37
+ * Se arma una vez y lo comparten el build y el servidor: es lo que garantiza que
38
+ * las entradas que se construyen y las que se resuelven por petición salgan de la
39
+ * misma fuente.
40
+ */
41
+ let current;
42
+ function getVariantCatalog() {
43
+ return current;
44
+ }
45
+ async function refreshVariantCatalog() {
46
+ current = await (0, catalog_1.buildVariantCatalog)({ pages: (0, pages_1.getPages)() });
47
+ return current;
48
+ }
49
+ async function resolveInputs() {
50
+ const catalog = await refreshVariantCatalog();
51
+ return {
52
+ client: (0, virtual_entries_1.clientBuildInputs)(catalog),
53
+ ssr: (0, pages_1.getViteSSRInput)(),
54
+ };
55
+ }
56
+ /**
57
+ * El plugin del framework.
58
+ *
59
+ * Devuelve dos: el propio y el guard que impide que `@ressjs/config` —valores de
60
+ * servidor, credenciales incluidas— termine en el paquete del navegador. El
61
+ * segundo no se agrega a mano justamente porque olvidarse es el modo de fallo que
62
+ * existe para evitar.
63
+ */
64
+ function ressPlugin(options = {}) {
65
+ return [ressCorePlugin(options), (0, config_1.configServerOnlyGuard)()];
66
+ }
67
+ function ressCorePlugin(options = {}) {
68
+ // Se recalcula en el hook `config`, después de `loadConfig`: hasta ese
69
+ // momento no se leyó `config.security`, así que acá sólo hay defaults.
70
+ let security = (0, security_1.resolveSecurityOptions)(options.security);
71
+ /** Raíz del proyecto según Vite; puede no ser el directorio de trabajo. */
72
+ let root = process.cwd();
73
+ let manifestWritten = false;
74
+ let extensions = { ...module_extensions_1.DEFAULT_EXTENSION_POLICY, ...options.extensions };
75
+ /** La configuración del proyecto, vigente desde el hook `config`. */
76
+ let config;
77
+ return {
78
+ name: 'ress',
79
+ /**
80
+ * Se aporta desde `config` y no desde `configResolved` para que la
81
+ * configuración del usuario se fusione encima siguiendo las reglas normales
82
+ * de Vite, en vez de ser sobrescrita por el framework.
83
+ */
84
+ async config(userConfig, env) {
85
+ // Se verifica acá, con la configuración del proyecto ya visible y antes de
86
+ // que el servidor escuche. Un aviso en la consola de un servidor de
87
+ // desarrollo no lo lee nadie; un error de arranque sí.
88
+ root = userConfig?.root ?? process.cwd();
89
+ // La configuración del proyecto se lee antes que nada: declara qué
90
+ // extensiones son código de usuario, bajo qué prefijo se sirve todo y qué
91
+ // variables llegan al navegador, y las tres decisiones condicionan lo que
92
+ // este hook devuelve.
93
+ config = await (0, config_2.loadConfig)(root, env?.mode ?? 'development');
94
+ extensions = { ...extensions, ...config.extensions };
95
+ // `config.security` es lo que declara el proyecto; sólo se pisa si quien
96
+ // arma el plugin aportó algo explícito.
97
+ security = (0, security_1.resolveSecurityOptions)(options.security ?? config.security);
98
+ await (0, config_2.writeProjectTypes)(root, config.platform);
99
+ // Se fija acá y no al construir el plugin: `current` es estado global del
100
+ // proceso, así que crear un objeto de plugin —dos configuraciones en un
101
+ // mismo proceso, un test que lo importa— reescribiría la política que leen
102
+ // el descubrimiento y el escaneo de rutas.
103
+ (0, module_extensions_1.setExtensionPolicy)(extensions);
104
+ if (env?.command === 'serve')
105
+ (0, security_1.assertDevServerHardening)(userConfig);
106
+ return {
107
+ ...(0, environments_1.defineEnvironments)(await resolveInputs()),
108
+ // Sólo al construir: en desarrollo el prefijo lo quita el pipeline de
109
+ // petición antes de que Vite vea la URL, así que su `base` sigue siendo
110
+ // `/`. Al construir sí tiene que quedar escrito en las referencias que
111
+ // los artefactos hacen entre sí.
112
+ ...(config.basePath && env?.command === 'build'
113
+ ? { base: config.basePath + '/' }
114
+ : {}),
115
+ // Sólo lo declarado en `publicEnv`. Lo demás no queda como `undefined`
116
+ // en el navegador: queda como código que nunca se generó.
117
+ ...(config.publicEnv.length
118
+ ? { define: (0, config_2.publicEnvDefines)(config.publicEnv, { strict: env?.command === 'build' }) }
119
+ : {}),
120
+ ...(env?.command === 'serve'
121
+ ? { server: (0, security_1.devServerDefaults)(security.dev.allowedOrigins) }
122
+ : {}),
123
+ };
124
+ },
125
+ /**
126
+ * Emite el manifest de rutas junto al build.
127
+ *
128
+ * Cargarlo en producción es lo que evita volver a recorrer el sistema de
129
+ * archivos en cada arranque para deducir algo que ya no puede cambiar.
130
+ *
131
+ * El hook corre una vez por entorno —cliente y servidor— y el manifest es
132
+ * el mismo para los dos, así que se escribe una sola vez.
133
+ */
134
+ async writeBundle(_options, bundle) {
135
+ // El cliente se construye primero, pero sólo el entorno SSR conoce los
136
+ // chunks que hay que registrar. Escribir durante el cliente dejaba un
137
+ // manifest válido en apariencia pero imposible de usar para cargar prod.
138
+ if (this.environment?.name !== 'ssr' || manifestWritten)
139
+ return;
140
+ manifestWritten = true;
141
+ const { writeFile, mkdir } = await import('node:fs/promises');
142
+ const path = await import('node:path');
143
+ const scanned = await (0, scan_1.scanRoutes)({
144
+ strict: true,
145
+ root,
146
+ pagesDir: path.join(root, 'app/pages'),
147
+ });
148
+ const data = (0, route_manifest_1.attachServerBuildChunks)({ ...scanned, middlewares: (0, middlewares_1.getMiddlewares)({ cwd: root }).map(middlewares_1.toMiddlewareEntry) }, bundle, root);
149
+ const target = path.resolve(root, environments_1.OUT_DIR.base);
150
+ await mkdir(target, { recursive: true });
151
+ await writeFile(path.join(target, 'route-manifest.json'), (0, manifest_1.serializeRouteManifest)(data), 'utf-8');
152
+ // Identifica este build. La caché incremental lo usa en la clave: un
153
+ // documento generado por otro build puede apuntar a artefactos que ya no
154
+ // existen, y la hidratación rota que produce no dice de dónde viene.
155
+ const { randomUUID } = await import('node:crypto');
156
+ await writeFile(path.join(target, environments_1.BUILD_ID_FILE), randomUUID(), 'utf-8');
157
+ },
158
+ resolveId(id) {
159
+ return (0, virtual_entries_1.resolveEntryId)(id);
160
+ },
161
+ load(id) {
162
+ const parsed = (0, virtual_entries_1.parseEntryId)(id);
163
+ if (!parsed || !current)
164
+ return undefined;
165
+ return (0, virtual_entries_1.generateEntryModule)(current, parsed.pageId, parsed.suffix);
166
+ },
167
+ /**
168
+ * Rearma el catálogo cuando cambia el **conjunto** de archivos, no su
169
+ * contenido: agregar o borrar una hoja de estilos cambia qué variantes
170
+ * declara una página, y eso tiene que reflejarse sin reiniciar. Los cambios
171
+ * de contenido los maneja la recarga en caliente de Vite.
172
+ */
173
+ configureServer(server) {
174
+ const declaresVariants = (file) => file.includes('app/pages/') || file.includes('app\\pages\\');
175
+ const rebuild = (file) => {
176
+ if (declaresVariants(file))
177
+ void refreshVariantCatalog();
178
+ };
179
+ server.watcher?.on('add', rebuild);
180
+ server.watcher?.on('unlink', rebuild);
181
+ // Las dos superficies de configuración. Observar sólo `ress.config` dejaba
182
+ // mudo el cambio de una capa de `config/`.
183
+ if (config) {
184
+ (0, config_2.watchConfig)(server, {
185
+ root,
186
+ mode: server.config?.mode ?? 'development',
187
+ current: () => config,
188
+ onReload: (next) => {
189
+ config = next;
190
+ },
191
+ });
192
+ }
193
+ },
194
+ };
195
+ }
@@ -0,0 +1,19 @@
1
+ import type { RouteManifestData } from '../routes/types';
2
+ export interface BuildOutputChunk {
3
+ type: 'chunk';
4
+ fileName: string;
5
+ facadeModuleId?: string | null;
6
+ isEntry?: boolean;
7
+ }
8
+ export interface BuildOutputAsset {
9
+ type: 'asset';
10
+ fileName: string;
11
+ }
12
+ export type BuildOutput = BuildOutputChunk | BuildOutputAsset;
13
+ export type BuildOutputBundle = Record<string, BuildOutput>;
14
+ /**
15
+ * Completa el manifest con los nombres que el bundler emitió realmente.
16
+ * `facadeModuleId` une cada chunk de entrada con su fuente sin adivinar reglas
17
+ * de nombres ni depender de cómo Rolldown sanee segmentos dinámicos.
18
+ */
19
+ export declare function attachServerBuildChunks(data: RouteManifestData, bundle: BuildOutputBundle, root: string): RouteManifestData;
@@ -0,0 +1,55 @@
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.attachServerBuildChunks = attachServerBuildChunks;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ /**
9
+ * Completa el manifest con los nombres que el bundler emitió realmente.
10
+ * `facadeModuleId` une cada chunk de entrada con su fuente sin adivinar reglas
11
+ * de nombres ni depender de cómo Rolldown sanee segmentos dinámicos.
12
+ */
13
+ function attachServerBuildChunks(data, bundle, root) {
14
+ const chunksBySource = new Map();
15
+ for (const output of Object.values(bundle)) {
16
+ if (output.type !== 'chunk' || !output.isEntry || !output.facadeModuleId)
17
+ continue;
18
+ const source = normalizeAbsolute(output.facadeModuleId, root);
19
+ chunksBySource.set(source, portableRelativeChunk(output.fileName));
20
+ }
21
+ const entries = data.entries.map((entry) => {
22
+ const source = normalizeAbsolute(entry.sourceFile, root);
23
+ const buildChunk = chunksBySource.get(source);
24
+ if (!buildChunk) {
25
+ throw new Error(`[ress] El build SSR no emitió un chunk de entrada para "${entry.sourceFile}" ` +
26
+ `(pageId "${entry.pageId}").`);
27
+ }
28
+ return { ...entry, server: { ...entry.server, buildChunk } };
29
+ });
30
+ // Los middlewares, igual que las páginas: producción los carga por el chunk
31
+ // registrado, sin volver a mirar las fuentes. Si el build no emitió uno, el
32
+ // manifest no se publica: un middleware sin chunk dejaría su ruta abierta.
33
+ const middlewares = data.middlewares?.map((middleware) => {
34
+ const buildChunk = chunksBySource.get(normalizeAbsolute(middleware.file, root));
35
+ if (!buildChunk) {
36
+ throw new Error(`[ress] El build SSR no emitió un chunk de entrada para el middleware "${middleware.file}".`);
37
+ }
38
+ return { ...middleware, server: { ...middleware.server, buildChunk } };
39
+ });
40
+ return { ...data, entries, ...(middlewares ? { middlewares } : {}) };
41
+ }
42
+ function normalizeAbsolute(file, root) {
43
+ const clean = file.replace(/[?#].*$/, '');
44
+ return node_path_1.default.resolve(root, clean).replace(/\\/g, '/');
45
+ }
46
+ function portableRelativeChunk(fileName) {
47
+ const normalized = fileName.replace(/\\/g, '/');
48
+ if (normalized.startsWith('/') ||
49
+ /^[A-Za-z]:\//.test(normalized) ||
50
+ normalized === '..' ||
51
+ normalized.startsWith('../')) {
52
+ throw new Error(`[ress] El build SSR produjo un nombre de chunk no portable: "${fileName}".`);
53
+ }
54
+ return normalized;
55
+ }
@@ -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__ || {};
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,55 @@
1
1
  import { RequestHandler } from 'express';
2
+ import type { ModuleLoader } from './runtime/module-loader';
3
+ import type { ClientPropsPolicy } from './security';
4
+ import { type CollectedHeadTag } from '@ressjs/assets';
2
5
  /**
3
6
  * RENDER MIDDLEWARE PARA EL ROUTER
7
+ *
8
+ * El render server se arma una sola vez por ruta, en la primera petición, y
9
+ * se reusa en las siguientes. Antes se creaba en cada `processRequest` sin
10
+ * `renderServer`, y `createRenderServer` barría `app/pages` con `fast-glob`
11
+ * en cada request: cien peticiones, cien globs síncronos contra el
12
+ * filesystem en producción.
13
+ *
14
+ * La primera petición espera a que `viteConfig` (que puede llegar como thunk
15
+ * en modo automático) esté resuelto, así que armar el render server recién
16
+ * ahí, y no al declarar el middleware, es lo que garantiza que ya tenga el
17
+ * `moduleLoader` correcto.
4
18
  */
5
19
  export declare function createRenderMiddleware(page: any, options: any): RequestHandler;
6
20
  export interface RenderOptions {
7
21
  url: string;
22
+ /**
23
+ * La página que el despachador ya resolvió. Obligatoria.
24
+ *
25
+ * Antes era opcional y, sin ella, el render la buscaba por URL con un
26
+ * criterio propio que no conoce grupos ni catch-all, escaneando `app/pages`
27
+ * en cada petición. Quien llamaba a `createRenderServer()` sólo con `url`
28
+ * tiene que pasar la página resuelta: `{ url, page: { route, file } }`.
29
+ */
30
+ page: {
31
+ route: string;
32
+ file: string;
33
+ pageId?: string;
34
+ abs?: string;
35
+ };
8
36
  props?: any;
37
+ /** Estado que dejaron los middlewares. Sólo viaja al cliente lo que la política declara. */
38
+ locals?: Record<string, unknown>;
39
+ /** `false`: la página no hidrata, así que no hay estado que calcular para el cliente. */
40
+ hydrate?: boolean;
9
41
  }
10
42
  export interface CreateRenderServerOptions {
11
- vite?: any;
43
+ /** Cargador de módulos. Ausente en producción: se importa el artefacto compilado. */
44
+ moduleLoader?: ModuleLoader;
45
+ /** Qué claves del estado del servidor viajan al cliente. */
46
+ clientProps?: ClientPropsPolicy;
47
+ isProduction?: boolean;
12
48
  }
13
49
  export declare function createRenderServer(options?: CreateRenderServerOptions): (renderOptions: RenderOptions) => Promise<{
14
50
  html: string;
15
- props: any;
51
+ props: Record<string, unknown>;
52
+ head: CollectedHeadTag[];
16
53
  route: string;
17
54
  page: string;
18
55
  }>;