@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.
- package/LICENSE +69 -0
- package/README.md +9 -515
- package/dist/client/entry.d.mts +2 -0
- package/dist/client/entry.mjs +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +8 -0
- package/dist/config/base-path.d.ts +44 -0
- package/dist/config/base-path.js +100 -0
- package/dist/config/codegen.d.ts +29 -0
- package/dist/config/codegen.js +131 -0
- package/dist/config/define.d.ts +8 -0
- package/dist/config/define.js +12 -0
- package/dist/config/env.d.ts +19 -0
- package/dist/config/env.js +40 -0
- package/dist/config/index.d.ts +18 -0
- package/dist/config/index.js +46 -0
- package/dist/config/load.d.ts +28 -0
- package/dist/config/load.js +82 -0
- package/dist/config/middleware.d.ts +18 -0
- package/dist/config/middleware.js +79 -0
- package/dist/config/rules.d.ts +59 -0
- package/dist/config/rules.js +162 -0
- package/dist/config/types.d.ts +189 -0
- package/dist/config/types.js +2 -0
- package/dist/config/validate.d.ts +19 -0
- package/dist/config/validate.js +143 -0
- package/dist/config/watch.d.ts +53 -0
- package/dist/config/watch.js +163 -0
- package/dist/fs/module-extensions.d.ts +105 -0
- package/dist/fs/module-extensions.js +213 -0
- package/dist/head/resolve.d.ts +5 -0
- package/dist/head/resolve.js +166 -0
- package/dist/head/types.d.ts +44 -0
- package/dist/head/types.js +2 -0
- package/dist/helpers/html-generator.d.ts +26 -6
- package/dist/helpers/html-generator.js +96 -138
- package/dist/helpers/middlewares.d.ts +32 -21
- package/dist/helpers/middlewares.js +181 -159
- package/dist/helpers/page-config-merge.d.ts +11 -0
- package/dist/helpers/page-config-merge.js +84 -0
- package/dist/helpers/request-handler.d.ts +15 -2
- package/dist/helpers/request-handler.js +164 -27
- package/dist/index.d.ts +35 -3
- package/dist/index.js +77 -8
- package/dist/isr/capture.d.ts +79 -0
- package/dist/isr/capture.js +222 -0
- package/dist/isr/handler.d.ts +49 -0
- package/dist/isr/handler.js +207 -0
- package/dist/isr/key.d.ts +26 -0
- package/dist/isr/key.js +59 -0
- package/dist/isr/page-config.d.ts +17 -0
- package/dist/isr/page-config.js +71 -0
- package/dist/isr/preview.d.ts +5 -0
- package/dist/isr/preview.js +38 -0
- package/dist/isr/public-request.d.ts +50 -0
- package/dist/isr/public-request.js +108 -0
- package/dist/isr/response.d.ts +41 -0
- package/dist/isr/response.js +128 -0
- package/dist/isr/store.d.ts +42 -0
- package/dist/isr/store.js +108 -0
- package/dist/isr/types.d.ts +62 -0
- package/dist/isr/types.js +2 -0
- package/dist/pages.d.ts +10 -9
- package/dist/pages.js +42 -261
- package/dist/platform.d.ts +11 -14
- package/dist/platform.js +18 -102
- package/dist/plugin/environments.d.ts +68 -0
- package/dist/plugin/environments.js +74 -0
- package/dist/plugin/index.d.ts +59 -0
- package/dist/plugin/index.js +195 -0
- package/dist/plugin/route-manifest.d.ts +19 -0
- package/dist/plugin/route-manifest.js +55 -0
- package/dist/plugin/virtual-entries.d.ts +26 -0
- package/dist/plugin/virtual-entries.js +80 -0
- package/dist/render.d.ts +39 -2
- package/dist/render.js +75 -77
- package/dist/router.d.ts +32 -13
- package/dist/router.js +180 -146
- package/dist/routes/dispatcher.d.ts +58 -0
- package/dist/routes/dispatcher.js +70 -0
- package/dist/routes/manifest.d.ts +17 -0
- package/dist/routes/manifest.js +62 -0
- package/dist/routes/match.d.ts +20 -0
- package/dist/routes/match.js +75 -0
- package/dist/routes/module.d.ts +14 -0
- package/dist/routes/module.js +30 -0
- package/dist/routes/parse.d.ts +31 -0
- package/dist/routes/parse.js +114 -0
- package/dist/routes/rank.d.ts +12 -0
- package/dist/routes/rank.js +49 -0
- package/dist/routes/router.d.ts +54 -0
- package/dist/routes/router.js +153 -0
- package/dist/routes/scan.d.ts +26 -0
- package/dist/routes/scan.js +98 -0
- package/dist/routes/types.d.ts +114 -0
- package/dist/routes/types.js +17 -0
- package/dist/runtime/dev-server.d.ts +64 -0
- package/dist/runtime/dev-server.js +94 -0
- package/dist/runtime/dev-styles.d.ts +13 -0
- package/dist/runtime/dev-styles.js +50 -0
- package/dist/runtime/module-loader.d.ts +55 -0
- package/dist/runtime/module-loader.js +122 -0
- package/dist/runtime/prod-server.d.ts +55 -0
- package/dist/runtime/prod-server.js +188 -0
- package/dist/runtime/template.d.ts +10 -0
- package/dist/runtime/template.js +33 -0
- package/dist/security/client-props.d.ts +25 -0
- package/dist/security/client-props.js +51 -0
- package/dist/security/config.d.ts +121 -0
- package/dist/security/config.js +77 -0
- package/dist/security/csp.d.ts +53 -0
- package/dist/security/csp.js +118 -0
- package/dist/security/dev-hardening.d.ts +46 -0
- package/dist/security/dev-hardening.js +65 -0
- package/dist/security/errors.d.ts +37 -0
- package/dist/security/errors.js +84 -0
- package/dist/security/escape.d.ts +40 -0
- package/dist/security/escape.js +90 -0
- package/dist/security/head-tags.d.ts +32 -0
- package/dist/security/head-tags.js +156 -0
- package/dist/security/headers.d.ts +76 -0
- package/dist/security/headers.js +278 -0
- package/dist/security/index.d.ts +24 -0
- package/dist/security/index.js +49 -0
- package/dist/security/serialize.d.ts +48 -0
- package/dist/security/serialize.js +146 -0
- package/dist/variants/assets.d.ts +33 -0
- package/dist/variants/assets.js +144 -0
- package/dist/variants/catalog.d.ts +33 -0
- package/dist/variants/catalog.js +79 -0
- package/dist/variants/platform-tokens.d.ts +9 -0
- package/dist/variants/platform-tokens.js +15 -0
- package/dist/variants/resolve.d.ts +40 -0
- package/dist/variants/resolve.js +90 -0
- package/dist/variants/suffix.d.ts +8 -0
- package/dist/variants/suffix.js +12 -0
- package/dist/variants/types.d.ts +72 -0
- package/dist/variants/types.js +2 -0
- package/package.json +23 -11
- package/dist/helpers/vite-config.d.ts +0 -10
- 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; } });
|