@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,146 @@
1
+ "use strict";
2
+ /**
3
+ * Serialización del estado que viaja al cliente.
4
+ *
5
+ * El JSON se embebe en un `<script>` inline, así que además de ser JSON válido
6
+ * tiene que sobrevivir a dos gramáticas más: la del HTML, que busca `</script`,
7
+ * `<!--` y `<!` dentro del elemento, y la de JavaScript, donde U+2028 y U+2029
8
+ * son terminadores de línea que parten un literal de cadena.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SerializationError = void 0;
12
+ exports.serializeState = serializeState;
13
+ exports.renderStateScript = renderStateScript;
14
+ exports.checkStateSize = checkStateSize;
15
+ const escape_1 = require("./escape");
16
+ /**
17
+ * Los cinco caracteres a escapar, y ninguno más.
18
+ *
19
+ * Escapar `<` neutraliza `</script>`, `<!--`, `<script` y `<!` de una sola vez,
20
+ * sin tener que reconocer secuencias. `&` va en la lista para que un analizador
21
+ * que decodifique entidades no pueda reconstruir un `<`.
22
+ *
23
+ * Los cinco reemplazos son escapes JSON válidos: `<` **es** `<` para
24
+ * `JSON.parse`, así que el valor no cambia, sólo su representación.
25
+ */
26
+ const ESCAPE_MAP = {
27
+ '<': '\\u003C',
28
+ '>': '\\u003E',
29
+ '&': '\\u0026',
30
+ '\u2028': '\\u2028',
31
+ '\u2029': '\\u2029',
32
+ };
33
+ const ESCAPE_RE = /[<>&\u2028\u2029]/g;
34
+ class SerializationError extends Error {
35
+ reason;
36
+ path;
37
+ constructor(reason, path) {
38
+ super(`[ress] no se pudo serializar el estado en ${path}: ${reason}`);
39
+ this.reason = reason;
40
+ this.path = path;
41
+ this.name = 'SerializationError';
42
+ }
43
+ }
44
+ exports.SerializationError = SerializationError;
45
+ /**
46
+ * Serializa un valor para embeberlo en un `<script>` inline.
47
+ *
48
+ * El resultado es JSON válido y `JSON.parse` lo devuelve idéntico al original.
49
+ */
50
+ function serializeState(value, path = '$') {
51
+ let json;
52
+ try {
53
+ json = JSON.stringify(value, createGuard(path));
54
+ }
55
+ catch (err) {
56
+ throw new SerializationError(describe(err), path);
57
+ }
58
+ if (json === undefined)
59
+ return 'undefined';
60
+ return json.replace(ESCAPE_RE, (c) => ESCAPE_MAP[c]);
61
+ }
62
+ /**
63
+ * Detecta lo que no es serializable **antes** de que falle, para poder decir
64
+ * dónde.
65
+ *
66
+ * `JSON.stringify` sobre una estructura cíclica dice "Converting circular
67
+ * structure to JSON" y no nombra la propiedad; sobre una función no dice nada,
68
+ * simplemente la omite. Las dos cosas son difíciles de diagnosticar en una
69
+ * página que dejó de hidratar.
70
+ *
71
+ * `Date` es la excepción deliberada: se convierte a su forma ISO y así llega al
72
+ * cliente. Es el comportamiento de siempre.
73
+ */
74
+ function createGuard(root) {
75
+ const seen = new WeakSet();
76
+ const paths = new WeakMap();
77
+ return function guard(key, value) {
78
+ const parent = this;
79
+ const path = key === ''
80
+ ? root
81
+ : `${(parent && paths.get(parent)) ?? root}${/^\d+$/.test(key) ? `[${key}]` : `.${key}`}`;
82
+ const type = typeof value;
83
+ if (type === 'function')
84
+ throw new SerializationError('es una función', path);
85
+ if (type === 'bigint')
86
+ throw new SerializationError('es un BigInt', path);
87
+ if (type === 'symbol')
88
+ throw new SerializationError('es un Symbol', path);
89
+ if (value instanceof Map)
90
+ throw new SerializationError('es un Map', path);
91
+ if (value instanceof Set)
92
+ throw new SerializationError('es un Set', path);
93
+ if (value && type === 'object' && !(value instanceof Date)) {
94
+ if (seen.has(value)) {
95
+ throw new SerializationError('la estructura tiene un ciclo', path);
96
+ }
97
+ seen.add(value);
98
+ paths.set(value, path);
99
+ }
100
+ return value;
101
+ };
102
+ }
103
+ function describe(err) {
104
+ if (err instanceof SerializationError)
105
+ throw err;
106
+ return err instanceof Error ? err.message : String(err);
107
+ }
108
+ /**
109
+ * El bloque `<script>` con el estado.
110
+ *
111
+ * Recibe el nonce cuando hay política de contenido: sin él, el bloque no se
112
+ * ejecuta y la hidratación no encuentra sus props.
113
+ */
114
+ function renderStateScript(blocks, nonce) {
115
+ const attrs = nonce ? ` nonce="${(0, escape_1.escapeAttrValue)(nonce)}"` : '';
116
+ const body = blocks
117
+ .map((b) => `window.${b.varName}=${serializeState(b.value, '$.' + b.varName)};`)
118
+ .join('');
119
+ return `<script${attrs}>${body}</script>`;
120
+ }
121
+ /**
122
+ * Cuánto pesa el estado que viaja.
123
+ *
124
+ * Se mide sobre el string **ya serializado y escapado**, que es lo que
125
+ * efectivamente se transfiere, y en bytes y no en caracteres, porque un
126
+ * carácter fuera de ASCII ocupa más de uno.
127
+ */
128
+ function checkStateSize(serialized, limitBytes, value) {
129
+ const bytes = Buffer.byteLength(serialized, 'utf8');
130
+ const overLimit = bytes > limitBytes;
131
+ const heaviest = overLimit && value && typeof value === 'object'
132
+ ? Object.entries(value)
133
+ .map(([key, v]) => ({ key, bytes: safeSize(v) }))
134
+ .sort((a, b) => b.bytes - a.bytes)
135
+ .slice(0, 3)
136
+ : [];
137
+ return { bytes, overLimit, heaviest };
138
+ }
139
+ function safeSize(value) {
140
+ try {
141
+ return Buffer.byteLength(JSON.stringify(value) ?? '', 'utf8');
142
+ }
143
+ catch {
144
+ return 0;
145
+ }
146
+ }
@@ -0,0 +1,33 @@
1
+ import type { AssetIndex, VariantCatalog } from './types';
2
+ import type { RouteManifest } from '../routes/types';
3
+ /**
4
+ * Artefactos en desarrollo.
5
+ *
6
+ * Vite sirve el módulo virtual directamente, así que el guion es su propia URL.
7
+ * Los estilos no se listan porque el módulo los importa y Vite los inyecta.
8
+ */
9
+ export declare function createDevAssetIndex(catalog: VariantCatalog, base?: string): AssetIndex;
10
+ /** Forma del manifest que emite el entorno de cliente. */
11
+ interface ManifestChunk {
12
+ file: string;
13
+ css?: string[];
14
+ name?: string;
15
+ src?: string;
16
+ }
17
+ /**
18
+ * Reconstruye el catálogo de producción desde artefactos del build.
19
+ *
20
+ * Volver a escanear `app/pages` al arrancar haría que un despliegue compuesto
21
+ * sólo por `dist/` perdiera todas sus variantes. El manifest del cliente ya
22
+ * enumera exactamente las entradas que se construyeron, incluida la variante.
23
+ */
24
+ export declare function createProdVariantCatalog(routes: RouteManifest, manifest: Record<string, ManifestChunk>): VariantCatalog;
25
+ /**
26
+ * Artefactos en producción, tomados del manifest del cliente.
27
+ *
28
+ * El manifest se lee una vez al arrancar y se pasa a un índice por página y
29
+ * variante, de modo que servir una petición sea una consulta en memoria y no una
30
+ * búsqueda.
31
+ */
32
+ export declare function createProdAssetIndex(catalog: VariantCatalog, manifest: Record<string, ManifestChunk>, base?: string): AssetIndex;
33
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDevAssetIndex = createDevAssetIndex;
4
+ exports.createProdVariantCatalog = createProdVariantCatalog;
5
+ exports.createProdAssetIndex = createProdAssetIndex;
6
+ const catalog_1 = require("./catalog");
7
+ const platform_tokens_1 = require("./platform-tokens");
8
+ const platform_1 = require("@ressjs/platform");
9
+ const dev_styles_1 = require("../runtime/dev-styles");
10
+ /**
11
+ * Artefactos en desarrollo.
12
+ *
13
+ * Vite sirve el módulo virtual directamente, así que el guion es su propia URL.
14
+ * Los estilos no se listan porque el módulo los importa y Vite los inyecta.
15
+ */
16
+ function createDevAssetIndex(catalog, base = '/') {
17
+ return {
18
+ find(pageId, variantSuffix) {
19
+ const variant = catalog.variantsByPage.get(pageId)?.find((v) => v.suffix === variantSuffix);
20
+ if (!variant)
21
+ return undefined;
22
+ // El prefijo lo agrega el framework, no Vite: el pipeline de petición lo
23
+ // quita antes de que Vite vea la URL, así que su propio `base` es `/`.
24
+ const prefix = joinBase(base, '').replace(/\/$/, '');
25
+ return {
26
+ js: joinBase(base, '@id/' + (0, catalog_1.virtualModuleIdFor)(pageId, variantSuffix)),
27
+ css: [],
28
+ ...(variant.styleFile ? { devStyles: [(0, dev_styles_1.directStyleUrl)(prefix, (0, dev_styles_1.projectFileUrl)(variant.styleFile))] } : {}),
29
+ };
30
+ },
31
+ };
32
+ }
33
+ /**
34
+ * Reconstruye el catálogo de producción desde artefactos del build.
35
+ *
36
+ * Volver a escanear `app/pages` al arrancar haría que un despliegue compuesto
37
+ * sólo por `dist/` perdiera todas sus variantes. El manifest del cliente ya
38
+ * enumera exactamente las entradas que se construyeron, incluida la variante.
39
+ */
40
+ function createProdVariantCatalog(routes, manifest) {
41
+ const buildEntries = [];
42
+ const variantsByPage = new Map();
43
+ const pagesById = new Map();
44
+ const seen = new Set();
45
+ for (const entry of routes.entries) {
46
+ if (entry.kind === 'api')
47
+ continue;
48
+ pagesById.set(entry.pageId, { route: entry.pattern, pageFile: entry.sourceFile });
49
+ }
50
+ for (const [key, chunk] of Object.entries(manifest)) {
51
+ const parsed = parseVirtualEntry(key) ?? (chunk.src ? parseVirtualEntry(chunk.src) : undefined);
52
+ if (!parsed || seen.has(`${parsed.pageId} ${parsed.suffix}`))
53
+ continue;
54
+ const page = pagesById.get(parsed.pageId);
55
+ if (!page)
56
+ continue;
57
+ seen.add(`${parsed.pageId} ${parsed.suffix}`);
58
+ const variant = {
59
+ suffix: parsed.suffix,
60
+ tags: (0, platform_1.parseVariantSuffix)(parsed.suffix, platform_tokens_1.defaultPlatformRegistry, key),
61
+ };
62
+ const variants = variantsByPage.get(parsed.pageId) ?? [];
63
+ variants.push(variant);
64
+ variantsByPage.set(parsed.pageId, variants);
65
+ buildEntries.push({
66
+ pageId: parsed.pageId,
67
+ route: page.route,
68
+ pageFile: page.pageFile,
69
+ variant,
70
+ virtualId: (0, catalog_1.virtualModuleIdFor)(parsed.pageId, parsed.suffix),
71
+ });
72
+ }
73
+ buildEntries.sort((a, b) => a.pageId === b.pageId
74
+ ? a.variant.suffix.localeCompare(b.variant.suffix)
75
+ : a.pageId.localeCompare(b.pageId));
76
+ for (const variants of variantsByPage.values()) {
77
+ variants.sort((a, b) => a.suffix.localeCompare(b.suffix));
78
+ }
79
+ return { buildEntries, variantsByPage, pagesById };
80
+ }
81
+ function parseVirtualEntry(value) {
82
+ if (!value.startsWith('ress:entry?'))
83
+ return undefined;
84
+ const query = new URLSearchParams(value.slice(value.indexOf('?') + 1));
85
+ const pageId = query.get('page');
86
+ if (!pageId)
87
+ return undefined;
88
+ return { pageId, suffix: query.get('variant') ?? '' };
89
+ }
90
+ /**
91
+ * Artefactos en producción, tomados del manifest del cliente.
92
+ *
93
+ * El manifest se lee una vez al arrancar y se pasa a un índice por página y
94
+ * variante, de modo que servir una petición sea una consulta en memoria y no una
95
+ * búsqueda.
96
+ */
97
+ function createProdAssetIndex(catalog, manifest, base = '/') {
98
+ const byPageAndVariant = new Map();
99
+ // El manifest indexa por el identificador de la entrada, que para un módulo
100
+ // virtual conserva el prefijo con el que se declaró.
101
+ const byEntryName = new Map();
102
+ for (const chunk of Object.values(manifest)) {
103
+ if (chunk.name)
104
+ byEntryName.set(chunk.name, chunk);
105
+ }
106
+ for (const entry of catalog.buildEntries) {
107
+ const entryName = (0, catalog_1.buildEntryNameFor)(entry.pageId, entry.variant.suffix);
108
+ const chunk = manifest[entry.virtualId] ??
109
+ manifest[NULL_BYTE + entry.virtualId] ??
110
+ byEntryName.get(entryName) ??
111
+ findByFilename(manifest, entryName);
112
+ if (!chunk)
113
+ continue;
114
+ byPageAndVariant.set(key(entry.pageId, entry.variant.suffix), {
115
+ js: joinBase(base, chunk.file),
116
+ css: (chunk.css ?? []).map((f) => joinBase(base, f)),
117
+ });
118
+ }
119
+ return { find: (pageId, variantSuffix) => byPageAndVariant.get(key(pageId, variantSuffix)) };
120
+ }
121
+ /** Prefijo con el que los empaquetadores marcan un módulo virtual resuelto. */
122
+ const NULL_BYTE = String.fromCharCode(0);
123
+ /**
124
+ * Respaldo por nombre de archivo.
125
+ *
126
+ * Los identificadores virtuales pueden aparecer saneados en el manifest según
127
+ * cómo el empaquetador normalice los caracteres del query, así que se busca
128
+ * además el artefacto cuyo nombre empiece por el de la entrada.
129
+ */
130
+ function findByFilename(manifest, entryName) {
131
+ const target = entryName.replace(/[^a-zA-Z0-9_.-]/g, '_');
132
+ return Object.values(manifest).find((c) => {
133
+ const file = c.file.split('/').pop() ?? '';
134
+ return file.startsWith(target + '-') || file.startsWith(target + '.');
135
+ });
136
+ }
137
+ const key = (pageId, variantSuffix) => `${pageId} ${variantSuffix}`;
138
+ function joinBase(base, file) {
139
+ // `/` ya es el prefijo absoluto; concatenarlo como `b + file` producía
140
+ // `//assets/...`, que el navegador interpreta como un host distinto. El
141
+ // mismo error rompía en desarrollo la URL `//@id/ress:entry...`.
142
+ const prefix = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
143
+ return `${prefix}/${file.replace(/^\/+/, '')}`;
144
+ }
@@ -0,0 +1,33 @@
1
+ import type { PageInfo } from '../pages';
2
+ import type { VariantCatalog, PlatformRegistry } from './types';
3
+ export interface CatalogOptions {
4
+ pages: PageInfo[];
5
+ registry?: PlatformRegistry;
6
+ }
7
+ /**
8
+ * Identificador estable de una página, compartido con el manifest de rutas.
9
+ *
10
+ * Los separadores y los corchetes de parámetro se sanean porque el identificador
11
+ * termina siendo el nombre de una entrada de build.
12
+ */
13
+ export declare function pageIdFromFile(pageFile: string): string;
14
+ /** Identificador del módulo virtual que hidrata esta página en esta variante. */
15
+ export declare function virtualModuleIdFor(pageId: string, suffix: string): string;
16
+ /**
17
+ * Nombre de la entrada de build de una página en una variante.
18
+ *
19
+ * El sufijo se une con `--` y sus puntos internos se vuelven guiones porque el
20
+ * empaquetador interpreta el ultimo punto del nombre de una entrada como su
21
+ * extension: con `page1__index.webview.android`, el artefacto de estilos sale
22
+ * como `page1__index.webview.css` y pierde el eje que lo distingue.
23
+ */
24
+ export declare function buildEntryNameFor(pageId: string, suffix: string): string;
25
+ /**
26
+ * Arma el catálogo: recorre las páginas y averigua qué variantes declara cada una.
27
+ *
28
+ * Corre **una sola vez** —al arrancar el servidor de desarrollo y al construir— y
29
+ * nunca durante una petición. Qué variantes existen lo deciden los archivos del
30
+ * proyecto, así que dos ejecuciones sobre el mismo árbol producen el mismo
31
+ * catálogo, y el build no depende de qué clientes visitaron el sitio antes.
32
+ */
33
+ export declare function buildVariantCatalog(opts: CatalogOptions): Promise<VariantCatalog>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pageIdFromFile = pageIdFromFile;
4
+ exports.virtualModuleIdFor = virtualModuleIdFor;
5
+ exports.buildEntryNameFor = buildEntryNameFor;
6
+ exports.buildVariantCatalog = buildVariantCatalog;
7
+ const module_extensions_1 = require("../fs/module-extensions");
8
+ const platform_tokens_1 = require("./platform-tokens");
9
+ const assets_1 = require("@ressjs/assets");
10
+ /**
11
+ * Identificador estable de una página, compartido con el manifest de rutas.
12
+ *
13
+ * Los separadores y los corchetes de parámetro se sanean porque el identificador
14
+ * termina siendo el nombre de una entrada de build.
15
+ */
16
+ function pageIdFromFile(pageFile) {
17
+ return pageFile
18
+ .replace(/^app\/pages\//, '')
19
+ .replace(/\.[^./]+$/, '')
20
+ .replace(/\//g, '__')
21
+ .replace(/\[([^\]]+)\]/g, '_$1_');
22
+ }
23
+ /** Identificador del módulo virtual que hidrata esta página en esta variante. */
24
+ function virtualModuleIdFor(pageId, suffix) {
25
+ return `ress:entry?page=${pageId}&variant=${encodeURIComponent(suffix)}`;
26
+ }
27
+ /**
28
+ * Nombre de la entrada de build de una página en una variante.
29
+ *
30
+ * El sufijo se une con `--` y sus puntos internos se vuelven guiones porque el
31
+ * empaquetador interpreta el ultimo punto del nombre de una entrada como su
32
+ * extension: con `page1__index.webview.android`, el artefacto de estilos sale
33
+ * como `page1__index.webview.css` y pierde el eje que lo distingue.
34
+ */
35
+ function buildEntryNameFor(pageId, suffix) {
36
+ return suffix ? `${pageId}--${suffix.replace(/\./g, '-')}` : pageId;
37
+ }
38
+ /**
39
+ * Arma el catálogo: recorre las páginas y averigua qué variantes declara cada una.
40
+ *
41
+ * Corre **una sola vez** —al arrancar el servidor de desarrollo y al construir— y
42
+ * nunca durante una petición. Qué variantes existen lo deciden los archivos del
43
+ * proyecto, así que dos ejecuciones sobre el mismo árbol producen el mismo
44
+ * catálogo, y el build no depende de qué clientes visitaron el sitio antes.
45
+ */
46
+ async function buildVariantCatalog(opts) {
47
+ const registry = opts.registry ?? platform_tokens_1.defaultPlatformRegistry;
48
+ const buildEntries = [];
49
+ const variantsByPage = new Map();
50
+ const pagesById = new Map();
51
+ for (const page of opts.pages) {
52
+ const pageId = pageIdFromFile(page.file);
53
+ // La extensión la recorta el módulo canónico: los archivos de estilos de una
54
+ // página `.jsx` se descubren igual que los de una `.tsx`.
55
+ const base = (0, module_extensions_1.stripScriptExtension)(page.file);
56
+ const variants = (await (0, assets_1.discoverStyleVariants)(base, registry)).map((variant) => ({
57
+ suffix: variant.suffix,
58
+ tags: variant.tags,
59
+ styleFile: variant.sourceFile,
60
+ }));
61
+ variantsByPage.set(pageId, variants);
62
+ pagesById.set(pageId, { route: page.route, pageFile: page.file });
63
+ for (const variant of variants) {
64
+ buildEntries.push({
65
+ pageId,
66
+ route: page.route,
67
+ pageFile: page.file,
68
+ variant,
69
+ virtualId: virtualModuleIdFor(pageId, variant.suffix),
70
+ });
71
+ }
72
+ }
73
+ // Orden estable: el resultado no debe depender de cómo el sistema de archivos
74
+ // devuelva los nombres.
75
+ buildEntries.sort((a, b) => a.pageId === b.pageId
76
+ ? a.variant.suffix.localeCompare(b.variant.suffix)
77
+ : a.pageId.localeCompare(b.pageId));
78
+ return { buildEntries, variantsByPage, pagesById };
79
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Qué tokens de plataforma son válidos y a qué eje pertenece cada uno.
3
+ *
4
+ * El vocabulario vive en `@ressjs/platform`, que no depende del router: es la
5
+ * misma autoridad que consultan el puente del WebView, el cliente y la
6
+ * detección por petición. Tener una lista propia acá significaría que el router
7
+ * puede discrepar con el resto del framework sobre qué es una plataforma.
8
+ */
9
+ export { createPlatformRegistry, defaultPlatformRegistry, DEFAULT_AXIS_PRECEDENCE as AXIS_PRECEDENCE, } from '@ressjs/platform';
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AXIS_PRECEDENCE = exports.defaultPlatformRegistry = exports.createPlatformRegistry = void 0;
4
+ /**
5
+ * Qué tokens de plataforma son válidos y a qué eje pertenece cada uno.
6
+ *
7
+ * El vocabulario vive en `@ressjs/platform`, que no depende del router: es la
8
+ * misma autoridad que consultan el puente del WebView, el cliente y la
9
+ * detección por petición. Tener una lista propia acá significaría que el router
10
+ * puede discrepar con el resto del framework sobre qué es una plataforma.
11
+ */
12
+ var platform_1 = require("@ressjs/platform");
13
+ Object.defineProperty(exports, "createPlatformRegistry", { enumerable: true, get: function () { return platform_1.createPlatformRegistry; } });
14
+ Object.defineProperty(exports, "defaultPlatformRegistry", { enumerable: true, get: function () { return platform_1.defaultPlatformRegistry; } });
15
+ Object.defineProperty(exports, "AXIS_PRECEDENCE", { enumerable: true, get: function () { return platform_1.DEFAULT_AXIS_PRECEDENCE; } });
@@ -0,0 +1,40 @@
1
+ import type { PlatformInfo } from '../platform';
2
+ import type { Axis, PlatformRegistry, Variant } from './types';
3
+ /**
4
+ * De todas las variantes que una página declara, cuál le toca a esta plataforma.
5
+ *
6
+ * Gana la que describe la situación con más precisión, entre las que le aplican.
7
+ * Para un teléfono Android dentro de un WebView, con las variantes `base`,
8
+ * `mobile` y `webview.android` declaradas, gana `webview.android`: las tres
9
+ * aplican, y esa es la que dice más sobre el cliente.
10
+ *
11
+ * Es la **única** implementación de esta decisión en el framework: la usan tanto
12
+ * el servidor por petición como el build. Que sea una sola es lo que impide que
13
+ * se construya una variante con un nombre y se busque con otro.
14
+ *
15
+ * El criterio es de especificidad y no una lista de prioridades fija, porque una
16
+ * lista no escala: agregar televisores o escritorio nativo obligaría a
17
+ * reescribirla entera y a decidir a mano dónde entra cada combinación nueva.
18
+ */
19
+ export declare function resolveVariant(platform: PlatformInfo, available: Variant[], registry: PlatformRegistry): Variant;
20
+ /**
21
+ * Devuelve la resolución con su explicación, para el diagnóstico de F-034 y el
22
+ * explorador de F-035. Un resultado inesperado sin el porqué deja en el mismo
23
+ * lugar que no tener nada.
24
+ */
25
+ export declare function explainResolution(platform: PlatformInfo, available: Variant[], registry: PlatformRegistry): {
26
+ selected: Variant;
27
+ candidates: Array<{
28
+ suffix: string;
29
+ applies: boolean;
30
+ specificity: number;
31
+ }>;
32
+ };
33
+ /**
34
+ * Por qué ejes varía una página.
35
+ *
36
+ * Es lo que su respuesta declara en `Vary`: si todas sus variantes son la base,
37
+ * el documento es el mismo para cualquier cliente y no varía por nada. Si sólo
38
+ * distingue televisores, varía por el dispositivo y por nada más.
39
+ */
40
+ export declare function varyingAxes(available: readonly Variant[]): Axis[];
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVariant = resolveVariant;
4
+ exports.explainResolution = explainResolution;
5
+ exports.varyingAxes = varyingAxes;
6
+ /**
7
+ * De todas las variantes que una página declara, cuál le toca a esta plataforma.
8
+ *
9
+ * Gana la que describe la situación con más precisión, entre las que le aplican.
10
+ * Para un teléfono Android dentro de un WebView, con las variantes `base`,
11
+ * `mobile` y `webview.android` declaradas, gana `webview.android`: las tres
12
+ * aplican, y esa es la que dice más sobre el cliente.
13
+ *
14
+ * Es la **única** implementación de esta decisión en el framework: la usan tanto
15
+ * el servidor por petición como el build. Que sea una sola es lo que impide que
16
+ * se construya una variante con un nombre y se busque con otro.
17
+ *
18
+ * El criterio es de especificidad y no una lista de prioridades fija, porque una
19
+ * lista no escala: agregar televisores o escritorio nativo obligaría a
20
+ * reescribirla entera y a decidir a mano dónde entra cada combinación nueva.
21
+ */
22
+ function resolveVariant(platform, available, registry) {
23
+ const base = available.find((v) => v.suffix === '');
24
+ // 1. Aplican las variantes cuyos tags matchean todos. La base no tiene tags,
25
+ // así que aplica siempre.
26
+ const applicable = available.filter((v) => v.tags.every((tag) => registry.matches(tag, platform)));
27
+ if (applicable.length === 0) {
28
+ if (!base) {
29
+ throw new Error('[ress] Ninguna variante aplica y no hay variante base. ' +
30
+ 'Toda página necesita una variante sin sufijo como respaldo.');
31
+ }
32
+ return base;
33
+ }
34
+ // 2. Gana la que describe la situación más precisa: más tags, más específica.
35
+ // 3. A igual cantidad, decide el eje más significativo que cada una declara.
36
+ return applicable.reduce((best, candidate) => compare(candidate, best, registry) > 0 ? candidate : best);
37
+ }
38
+ /** Positivo si `a` es más específica que `b`. */
39
+ function compare(a, b, registry) {
40
+ if (a.tags.length !== b.tags.length)
41
+ return a.tags.length - b.tags.length;
42
+ // Empate de especificidad: gana la que declara el eje más significativo.
43
+ // `category` cambia la página más profundamente que `device`, porque un
44
+ // WebView embebido difiere de un navegador más de lo que un teléfono difiere
45
+ // de una tablet.
46
+ const rankA = topAxisRank(a, registry);
47
+ const rankB = topAxisRank(b, registry);
48
+ if (rankA !== rankB)
49
+ return rankB - rankA;
50
+ // Sin criterio que las distinga, el orden alfabético del sufijo mantiene el
51
+ // resultado estable entre ejecuciones.
52
+ return b.suffix.localeCompare(a.suffix);
53
+ }
54
+ /** Posición del eje más significativo que declara la variante. Menor es mejor. */
55
+ function topAxisRank(v, registry) {
56
+ if (v.tags.length === 0)
57
+ return registry.axisPrecedence.length;
58
+ return Math.min(...v.tags.map((t) => registry.axisPrecedence.indexOf(t.axis)));
59
+ }
60
+ /**
61
+ * Devuelve la resolución con su explicación, para el diagnóstico de F-034 y el
62
+ * explorador de F-035. Un resultado inesperado sin el porqué deja en el mismo
63
+ * lugar que no tener nada.
64
+ */
65
+ function explainResolution(platform, available, registry) {
66
+ const selected = resolveVariant(platform, available, registry);
67
+ return {
68
+ selected,
69
+ candidates: available.map((v) => ({
70
+ suffix: v.suffix,
71
+ applies: v.tags.every((tag) => registry.matches(tag, platform)),
72
+ specificity: v.tags.length,
73
+ })),
74
+ };
75
+ }
76
+ /**
77
+ * Por qué ejes varía una página.
78
+ *
79
+ * Es lo que su respuesta declara en `Vary`: si todas sus variantes son la base,
80
+ * el documento es el mismo para cualquier cliente y no varía por nada. Si sólo
81
+ * distingue televisores, varía por el dispositivo y por nada más.
82
+ */
83
+ function varyingAxes(available) {
84
+ const axes = new Set();
85
+ for (const variant of available) {
86
+ for (const tag of variant.tags)
87
+ axes.add(tag.axis);
88
+ }
89
+ return [...axes];
90
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Convierte el sufijo de un archivo de variante en tags clasificados.
3
+ *
4
+ * La implementación vive en `@ressjs/platform` junto al registro que la
5
+ * alimenta: separarlas dejaría que el parseo aceptara tokens que el registro no
6
+ * reconoce, o al revés.
7
+ */
8
+ export { parseVariantSuffix } from '@ressjs/platform';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseVariantSuffix = void 0;
4
+ /**
5
+ * Convierte el sufijo de un archivo de variante en tags clasificados.
6
+ *
7
+ * La implementación vive en `@ressjs/platform` junto al registro que la
8
+ * alimenta: separarlas dejaría que el parseo aceptara tokens que el registro no
9
+ * reconoce, o al revés.
10
+ */
11
+ var platform_1 = require("@ressjs/platform");
12
+ Object.defineProperty(exports, "parseVariantSuffix", { enumerable: true, get: function () { return platform_1.parseVariantSuffix; } });