@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,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDispatcher = createDispatcher;
|
|
4
|
+
exports.runMiddlewareChain = runMiddlewareChain;
|
|
5
|
+
/**
|
|
6
|
+
* Un único punto de entrada para todas las rutas.
|
|
7
|
+
*
|
|
8
|
+
* El modelo anterior registraba una ruta de Express por página, lo que obligaba
|
|
9
|
+
* a traducir cada patrón a la sintaxis de Express —que no puede expresar un
|
|
10
|
+
* catch-all opcional ni un grupo— y dejaba la precedencia en manos del orden de
|
|
11
|
+
* registro. Acá el manifest decide, y Express sólo ve un middleware.
|
|
12
|
+
*
|
|
13
|
+
* Las cadenas se construyen una vez, no por petición. `chains` se reemplaza
|
|
14
|
+
* entero cuando el manifest cambia en desarrollo, de modo que una petición nunca
|
|
15
|
+
* ve una mezcla de dos versiones.
|
|
16
|
+
*/
|
|
17
|
+
function createDispatcher(getState) {
|
|
18
|
+
return async (req, res, next) => {
|
|
19
|
+
const { manifest, chains } = getState();
|
|
20
|
+
const match = manifest.match(req.path ?? req.url ?? '/');
|
|
21
|
+
// Sin match no es un 404 todavía: puede haber middlewares detrás que sepan
|
|
22
|
+
// atenderla, y decidirlo acá les quitaría la oportunidad.
|
|
23
|
+
if (!match)
|
|
24
|
+
return next();
|
|
25
|
+
const chain = chains.get(match.entry.pageId);
|
|
26
|
+
if (!chain)
|
|
27
|
+
return next();
|
|
28
|
+
req.ressParams = match.params;
|
|
29
|
+
req.params = match.params;
|
|
30
|
+
req.ressRoute = match.entry;
|
|
31
|
+
runMiddlewareChain(chain, req, res, next);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ejecuta la cadena respetando el contrato de Express.
|
|
36
|
+
*
|
|
37
|
+
* Cada middleware decide si sigue: `next()` avanza, `next(error)` corta y
|
|
38
|
+
* propaga. Un throw síncrono se convierte en `next(error)` para que no quede una
|
|
39
|
+
* petición colgada.
|
|
40
|
+
*/
|
|
41
|
+
function runMiddlewareChain(chain, req, res, done) {
|
|
42
|
+
let index = 0;
|
|
43
|
+
const step = (error) => {
|
|
44
|
+
if (error)
|
|
45
|
+
return done(error);
|
|
46
|
+
const handler = chain[index++];
|
|
47
|
+
if (!handler)
|
|
48
|
+
return done();
|
|
49
|
+
// Un middleware asíncrono puede llamar a `next()` y rechazar después. Sin
|
|
50
|
+
// esta guarda, el rechazo volvería a entrar a la cadena sobre una respuesta
|
|
51
|
+
// que ya se envió, y el fallo sería un ERR_HTTP_HEADERS_SENT en vez de la
|
|
52
|
+
// página de error.
|
|
53
|
+
let advanced = false;
|
|
54
|
+
const once = (err) => {
|
|
55
|
+
if (advanced)
|
|
56
|
+
return;
|
|
57
|
+
advanced = true;
|
|
58
|
+
step(err);
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
const result = handler(req, res, once);
|
|
62
|
+
if (result instanceof Promise)
|
|
63
|
+
result.catch(once);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
once(err);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
step();
|
|
70
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RouteManifest, type RouteManifestData } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* El manifest ya indexado.
|
|
4
|
+
*
|
|
5
|
+
* Las rutas estáticas —la mayoría de un proyecto— se resuelven con una consulta
|
|
6
|
+
* a un mapa. Sólo las que tienen parámetros recorren la lista, y ya viene
|
|
7
|
+
* ordenada por precedencia, así que la primera que matchea es la que gana.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createRouteManifest(data: RouteManifestData): RouteManifest;
|
|
10
|
+
export declare function serializeRouteManifest(data: RouteManifestData): string;
|
|
11
|
+
/**
|
|
12
|
+
* Lee un manifest emitido por el build.
|
|
13
|
+
*
|
|
14
|
+
* La versión se comprueba a propósito: un manifest de un formato anterior
|
|
15
|
+
* produciría fallos de ruteo difíciles de atribuir, y el mensaje dice qué hacer.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadRouteManifest(raw: string): RouteManifest;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRouteManifest = createRouteManifest;
|
|
4
|
+
exports.serializeRouteManifest = serializeRouteManifest;
|
|
5
|
+
exports.loadRouteManifest = loadRouteManifest;
|
|
6
|
+
const match_1 = require("./match");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
/**
|
|
9
|
+
* El manifest ya indexado.
|
|
10
|
+
*
|
|
11
|
+
* Las rutas estáticas —la mayoría de un proyecto— se resuelven con una consulta
|
|
12
|
+
* a un mapa. Sólo las que tienen parámetros recorren la lista, y ya viene
|
|
13
|
+
* ordenada por precedencia, así que la primera que matchea es la que gana.
|
|
14
|
+
*/
|
|
15
|
+
function createRouteManifest(data) {
|
|
16
|
+
const entries = data.entries;
|
|
17
|
+
const byPageId = new Map(entries.map((e) => [e.pageId, e]));
|
|
18
|
+
const staticIndex = new Map();
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.segments.every((s) => s.kind === 'static')) {
|
|
21
|
+
staticIndex.set(`${entry.kind} ${entry.pattern}`, entry);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
entries,
|
|
26
|
+
middlewares: data.middlewares,
|
|
27
|
+
match(pathname, options = {}) {
|
|
28
|
+
const kinds = options.kinds ?? ['page'];
|
|
29
|
+
const clean = '/' + pathname.split('?')[0].split('#')[0].split('/').filter(Boolean).join('/');
|
|
30
|
+
for (const kind of kinds) {
|
|
31
|
+
const exact = staticIndex.get(`${kind} ${clean}`);
|
|
32
|
+
if (exact)
|
|
33
|
+
return { entry: exact, params: {} };
|
|
34
|
+
}
|
|
35
|
+
return (0, match_1.matchEntries)(entries, pathname, options);
|
|
36
|
+
},
|
|
37
|
+
get: (pageId) => byPageId.get(pageId),
|
|
38
|
+
byKind: (kind) => entries.filter((e) => e.kind === kind),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function serializeRouteManifest(data) {
|
|
42
|
+
return JSON.stringify(data, null, 2);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Lee un manifest emitido por el build.
|
|
46
|
+
*
|
|
47
|
+
* La versión se comprueba a propósito: un manifest de un formato anterior
|
|
48
|
+
* produciría fallos de ruteo difíciles de atribuir, y el mensaje dice qué hacer.
|
|
49
|
+
*/
|
|
50
|
+
function loadRouteManifest(raw) {
|
|
51
|
+
const data = JSON.parse(raw);
|
|
52
|
+
if (data.version !== types_1.ROUTE_MANIFEST_VERSION) {
|
|
53
|
+
throw new Error(`[ress] El manifest de rutas es de la versión ${data.version} y este ress.js espera la ` +
|
|
54
|
+
`${types_1.ROUTE_MANIFEST_VERSION}. Volvé a construir el proyecto.`);
|
|
55
|
+
}
|
|
56
|
+
// Un manifest de build sin la lista de middlewares arrancaría sin ellos. Si
|
|
57
|
+
// falta, el build está incompleto: mejor no arrancar.
|
|
58
|
+
if (!Array.isArray(data.middlewares)) {
|
|
59
|
+
throw new Error('[ress] El manifest de rutas no registra los middlewares del build. Volvé a construir el proyecto.');
|
|
60
|
+
}
|
|
61
|
+
return createRouteManifest(data);
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MatchOptions, RouteEntry, RouteMatch, RouteSegment } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Parte una URL en segmentos.
|
|
4
|
+
*
|
|
5
|
+
* Se descartan la consulta y el fragmento, y la barra final no distingue: `/about`
|
|
6
|
+
* y `/about/` son la misma ruta.
|
|
7
|
+
*/
|
|
8
|
+
export declare function splitPath(pathname: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* ¿Esta ruta atiende esta URL?
|
|
11
|
+
*
|
|
12
|
+
* Sin expresiones regulares: se recorre segmento a segmento. Una regex construida
|
|
13
|
+
* a partir de un patrón es difícil de auditar y fácil de romper con un carácter
|
|
14
|
+
* que había que escapar — y el modelo anterior tenía justamente ese defecto:
|
|
15
|
+
* `/page1` matcheaba `/page1/cualquier/cosa` porque su regex no exigía consumir
|
|
16
|
+
* la URL entera.
|
|
17
|
+
*/
|
|
18
|
+
export declare function matchSegments(segments: readonly RouteSegment[], parts: readonly string[]): Record<string, string | string[]> | undefined;
|
|
19
|
+
/** La primera entrada que atiende esta URL, recorriendo en orden de precedencia. */
|
|
20
|
+
export declare function matchEntries(entries: readonly RouteEntry[], pathname: string, options?: MatchOptions): RouteMatch | undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitPath = splitPath;
|
|
4
|
+
exports.matchSegments = matchSegments;
|
|
5
|
+
exports.matchEntries = matchEntries;
|
|
6
|
+
/**
|
|
7
|
+
* Parte una URL en segmentos.
|
|
8
|
+
*
|
|
9
|
+
* Se descartan la consulta y el fragmento, y la barra final no distingue: `/about`
|
|
10
|
+
* y `/about/` son la misma ruta.
|
|
11
|
+
*/
|
|
12
|
+
function splitPath(pathname) {
|
|
13
|
+
const clean = pathname.split('?')[0].split('#')[0];
|
|
14
|
+
return clean.split('/').filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* ¿Esta ruta atiende esta URL?
|
|
18
|
+
*
|
|
19
|
+
* Sin expresiones regulares: se recorre segmento a segmento. Una regex construida
|
|
20
|
+
* a partir de un patrón es difícil de auditar y fácil de romper con un carácter
|
|
21
|
+
* que había que escapar — y el modelo anterior tenía justamente ese defecto:
|
|
22
|
+
* `/page1` matcheaba `/page1/cualquier/cosa` porque su regex no exigía consumir
|
|
23
|
+
* la URL entera.
|
|
24
|
+
*/
|
|
25
|
+
function matchSegments(segments, parts) {
|
|
26
|
+
const params = {};
|
|
27
|
+
let index = 0;
|
|
28
|
+
for (const segment of segments) {
|
|
29
|
+
if (segment.kind === 'catchAll' || segment.kind === 'optionalCatchAll') {
|
|
30
|
+
const rest = parts.slice(index).map(decode);
|
|
31
|
+
// Un catch-all obligatorio necesita al menos un segmento; el opcional
|
|
32
|
+
// acepta ninguno y entrega una lista vacía.
|
|
33
|
+
if (segment.kind === 'catchAll' && rest.length === 0)
|
|
34
|
+
return undefined;
|
|
35
|
+
params[segment.value] = rest;
|
|
36
|
+
return params;
|
|
37
|
+
}
|
|
38
|
+
const part = parts[index];
|
|
39
|
+
if (part === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (segment.kind === 'static') {
|
|
42
|
+
if (part !== segment.value)
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
params[segment.value] = decode(part);
|
|
47
|
+
}
|
|
48
|
+
index++;
|
|
49
|
+
}
|
|
50
|
+
// La URL tiene que quedar consumida entera: lo que sobra no lo atiende esta ruta.
|
|
51
|
+
return index === parts.length ? params : undefined;
|
|
52
|
+
}
|
|
53
|
+
function decode(value) {
|
|
54
|
+
try {
|
|
55
|
+
return decodeURIComponent(value);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Una secuencia de escape inválida no es motivo para fallar la petición: el
|
|
59
|
+
// valor crudo es lo que el cliente pidió.
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** La primera entrada que atiende esta URL, recorriendo en orden de precedencia. */
|
|
64
|
+
function matchEntries(entries, pathname, options = {}) {
|
|
65
|
+
const kinds = options.kinds ?? ['page'];
|
|
66
|
+
const parts = splitPath(pathname);
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (!kinds.includes(entry.kind))
|
|
69
|
+
continue;
|
|
70
|
+
const params = matchSegments(entry.segments, parts);
|
|
71
|
+
if (params)
|
|
72
|
+
return { entry, params };
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RouteEntry } from './types';
|
|
2
|
+
export interface ResolveServerModuleContext {
|
|
3
|
+
root: string;
|
|
4
|
+
isProduction: boolean;
|
|
5
|
+
/** Directorio del build SSR, relativo a `root` o absoluto. */
|
|
6
|
+
serverOutDir: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resuelve el módulo de una entrada desde la identidad que registró el build.
|
|
10
|
+
*
|
|
11
|
+
* En producción no se vuelve a derivar un nombre desde `sourceFile`: esa regla
|
|
12
|
+
* se rompe apenas el bundler cambia un nombre, agrega un hash o mueve un chunk.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveServerModule(entry: Pick<RouteEntry, 'pageId' | 'server'>, context: ResolveServerModuleContext): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
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.resolveServerModule = resolveServerModule;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
/**
|
|
9
|
+
* Resuelve el módulo de una entrada desde la identidad que registró el build.
|
|
10
|
+
*
|
|
11
|
+
* En producción no se vuelve a derivar un nombre desde `sourceFile`: esa regla
|
|
12
|
+
* se rompe apenas el bundler cambia un nombre, agrega un hash o mueve un chunk.
|
|
13
|
+
*/
|
|
14
|
+
function resolveServerModule(entry, context) {
|
|
15
|
+
if (!context.isProduction)
|
|
16
|
+
return entry.server.devId;
|
|
17
|
+
const chunk = entry.server.buildChunk;
|
|
18
|
+
if (!chunk) {
|
|
19
|
+
throw new Error(`[ress] La entrada "${entry.pageId}" no tiene módulo de servidor compilado. ` +
|
|
20
|
+
'Volvé a construir el proyecto.');
|
|
21
|
+
}
|
|
22
|
+
const outDir = node_path_1.default.resolve(context.root, context.serverOutDir);
|
|
23
|
+
const target = node_path_1.default.resolve(outDir, chunk);
|
|
24
|
+
const relative = node_path_1.default.relative(outDir, target);
|
|
25
|
+
if (relative === '..' || relative.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(relative)) {
|
|
26
|
+
throw new Error(`[ress] El chunk de servidor de "${entry.pageId}" queda fuera del directorio de build: ` +
|
|
27
|
+
`"${chunk}".`);
|
|
28
|
+
}
|
|
29
|
+
return target;
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RouteEntryKind, RouteSegment } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Convierte la ubicación de un archivo en un patrón de ruta.
|
|
4
|
+
*
|
|
5
|
+
* Notación:
|
|
6
|
+
*
|
|
7
|
+
* | En el archivo | En la URL |
|
|
8
|
+
* |---|---|
|
|
9
|
+
* | `users/perfil.tsx` | `/users/perfil` |
|
|
10
|
+
* | `users/index.tsx` | `/users` |
|
|
11
|
+
* | `users/[id].tsx` | `/users/:id` |
|
|
12
|
+
* | `docs/[...slug].tsx` | `/docs/` y todo lo que siga |
|
|
13
|
+
* | `shop/[[...filtros]].tsx` | `/shop` y todo lo que siga |
|
|
14
|
+
* | `(marketing)/about.tsx` | `/about` — el grupo no aporta segmento |
|
|
15
|
+
*/
|
|
16
|
+
export interface ParsedRoute {
|
|
17
|
+
pattern: string;
|
|
18
|
+
segments: RouteSegment[];
|
|
19
|
+
paramNames: string[];
|
|
20
|
+
groups: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function parseRoutePattern(sourceFile: string, pagesDir?: string): ParsedRoute;
|
|
23
|
+
/**
|
|
24
|
+
* Identificador estable de una página.
|
|
25
|
+
*
|
|
26
|
+
* Es la misma clave que usa el catálogo de variantes de F-016, para que las dos
|
|
27
|
+
* estructuras hablen del mismo objeto sin traducir nada entre ellas.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pageIdFromFile(sourceFile: string, pagesDir?: string): string;
|
|
30
|
+
/** Qué clase de entrada es, según dónde vive el archivo. */
|
|
31
|
+
export declare function classifyEntry(sourceFile: string, pagesDir?: string): RouteEntryKind;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRoutePattern = parseRoutePattern;
|
|
4
|
+
exports.pageIdFromFile = pageIdFromFile;
|
|
5
|
+
exports.classifyEntry = classifyEntry;
|
|
6
|
+
const EXTENSION = /\.(tsx|ts|jsx|js|mts|mjs)$/;
|
|
7
|
+
function parseRoutePattern(sourceFile, pagesDir = 'app/pages') {
|
|
8
|
+
const relative = stripPagesDir(sourceFile, pagesDir).replace(EXTENSION, '');
|
|
9
|
+
const segments = [];
|
|
10
|
+
const paramNames = [];
|
|
11
|
+
const groups = [];
|
|
12
|
+
const parts = relative.split('/').filter(Boolean);
|
|
13
|
+
for (const [index, raw] of parts.entries()) {
|
|
14
|
+
// Un grupo organiza archivos sin aparecer en la URL.
|
|
15
|
+
if (raw.startsWith('(') && raw.endsWith(')')) {
|
|
16
|
+
groups.push(raw.slice(1, -1));
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
// `index` nombra la ruta de su carpeta, así que no aporta segmento propio.
|
|
20
|
+
if (raw === 'index' && index === parts.length - 1)
|
|
21
|
+
continue;
|
|
22
|
+
const segment = parseSegment(raw, sourceFile);
|
|
23
|
+
// Todo lo que siga a un catch-all sería inalcanzable: el catch-all ya
|
|
24
|
+
// consumió el resto de la URL.
|
|
25
|
+
const previous = segments[segments.length - 1];
|
|
26
|
+
if (previous && (previous.kind === 'catchAll' || previous.kind === 'optionalCatchAll')) {
|
|
27
|
+
throw new Error(`[ress] "${sourceFile}" pone un segmento después de "[...${previous.value}]". ` +
|
|
28
|
+
'Un catch-all consume el resto de la URL, así que nada que lo siga puede alcanzarse.');
|
|
29
|
+
}
|
|
30
|
+
if (segment.kind !== 'static') {
|
|
31
|
+
if (paramNames.includes(segment.value)) {
|
|
32
|
+
throw new Error(`[ress] "${sourceFile}" usa el parámetro "${segment.value}" dos veces. ` +
|
|
33
|
+
'Cada parámetro tiene que tener un nombre distinto para poder leerse.');
|
|
34
|
+
}
|
|
35
|
+
paramNames.push(segment.value);
|
|
36
|
+
}
|
|
37
|
+
segments.push(segment);
|
|
38
|
+
}
|
|
39
|
+
return { pattern: toPattern(segments), segments, paramNames, groups };
|
|
40
|
+
}
|
|
41
|
+
function parseSegment(raw, sourceFile) {
|
|
42
|
+
const open = raw.indexOf('[');
|
|
43
|
+
const close = raw.lastIndexOf(']');
|
|
44
|
+
if (open === -1 && close === -1)
|
|
45
|
+
return { kind: 'static', value: raw };
|
|
46
|
+
if (open !== 0 || close !== raw.length - 1) {
|
|
47
|
+
throw new Error(`[ress] "${sourceFile}" tiene corchetes desbalanceados o mezclados con texto en "${raw}". ` +
|
|
48
|
+
'Un segmento con parámetro se escribe entero entre corchetes: [id], [...slug], [[...slug]].');
|
|
49
|
+
}
|
|
50
|
+
const inner = raw.slice(1, -1);
|
|
51
|
+
// `[[...slug]]` — el doble corchete lo vuelve opcional.
|
|
52
|
+
if (inner.startsWith('[') && inner.endsWith(']')) {
|
|
53
|
+
const name = requireName(inner.slice(1, -1).replace(/^\.\.\./, ''), raw, sourceFile);
|
|
54
|
+
if (!inner.slice(1, -1).startsWith('...')) {
|
|
55
|
+
throw new Error(`[ress] "${sourceFile}" escribe "${raw}" con doble corchete pero sin "...". ` +
|
|
56
|
+
'El doble corchete es para un catch-all opcional: [[...slug]].');
|
|
57
|
+
}
|
|
58
|
+
return { kind: 'optionalCatchAll', value: name };
|
|
59
|
+
}
|
|
60
|
+
if (inner.startsWith('...')) {
|
|
61
|
+
return { kind: 'catchAll', value: requireName(inner.slice(3), raw, sourceFile) };
|
|
62
|
+
}
|
|
63
|
+
return { kind: 'dynamic', value: requireName(inner, raw, sourceFile) };
|
|
64
|
+
}
|
|
65
|
+
function requireName(name, raw, sourceFile) {
|
|
66
|
+
if (!name) {
|
|
67
|
+
throw new Error(`[ress] "${sourceFile}" declara el parámetro "${raw}" sin nombre. ` +
|
|
68
|
+
'Sin nombre no hay forma de leer su valor desde la página.');
|
|
69
|
+
}
|
|
70
|
+
return name;
|
|
71
|
+
}
|
|
72
|
+
function toPattern(segments) {
|
|
73
|
+
if (segments.length === 0)
|
|
74
|
+
return '/';
|
|
75
|
+
return ('/' +
|
|
76
|
+
segments
|
|
77
|
+
.map((s) => {
|
|
78
|
+
if (s.kind === 'static')
|
|
79
|
+
return s.value;
|
|
80
|
+
if (s.kind === 'dynamic')
|
|
81
|
+
return `[${s.value}]`;
|
|
82
|
+
if (s.kind === 'catchAll')
|
|
83
|
+
return `[...${s.value}]`;
|
|
84
|
+
return `[[...${s.value}]]`;
|
|
85
|
+
})
|
|
86
|
+
.join('/'));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Identificador estable de una página.
|
|
90
|
+
*
|
|
91
|
+
* Es la misma clave que usa el catálogo de variantes de F-016, para que las dos
|
|
92
|
+
* estructuras hablen del mismo objeto sin traducir nada entre ellas.
|
|
93
|
+
*/
|
|
94
|
+
function pageIdFromFile(sourceFile, pagesDir = 'app/pages') {
|
|
95
|
+
return stripPagesDir(sourceFile, pagesDir)
|
|
96
|
+
.replace(EXTENSION, '')
|
|
97
|
+
.replace(/\//g, '__')
|
|
98
|
+
.replace(/\[([^\]]+)\]/g, '_$1_');
|
|
99
|
+
}
|
|
100
|
+
/** Qué clase de entrada es, según dónde vive el archivo. */
|
|
101
|
+
function classifyEntry(sourceFile, pagesDir = 'app/pages') {
|
|
102
|
+
const relative = stripPagesDir(sourceFile, pagesDir);
|
|
103
|
+
if (relative.startsWith('api/'))
|
|
104
|
+
return 'api';
|
|
105
|
+
if (/(^|\/)_error(\.|\/)|(^|\/)error\//.test(relative))
|
|
106
|
+
return 'error';
|
|
107
|
+
return 'page';
|
|
108
|
+
}
|
|
109
|
+
function stripPagesDir(sourceFile, pagesDir) {
|
|
110
|
+
const normalized = sourceFile.replace(/\\/g, '/');
|
|
111
|
+
const marker = pagesDir.replace(/\\/g, '/').replace(/\/$/, '') + '/';
|
|
112
|
+
const index = normalized.indexOf(marker);
|
|
113
|
+
return index === -1 ? normalized.replace(/^\//, '') : normalized.slice(index + marker.length);
|
|
114
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RouteEntry, RouteSegment } from './types';
|
|
2
|
+
/** Clave de comparación de una ruta: la especificidad de cada segmento, en orden. */
|
|
3
|
+
export declare function computeRank(segments: readonly RouteSegment[]): number[];
|
|
4
|
+
/**
|
|
5
|
+
* Orden en que se prueban las rutas.
|
|
6
|
+
*
|
|
7
|
+
* Se compara segmento a segmento; el primero que difiere decide. A igual
|
|
8
|
+
* especificidad gana la más larga —describe más de la URL— y, si aún empatan, el
|
|
9
|
+
* orden alfabético del patrón, para que el resultado no dependa de en qué orden
|
|
10
|
+
* el sistema de archivos devolvió los nombres.
|
|
11
|
+
*/
|
|
12
|
+
export declare function compareEntries(a: RouteEntry, b: RouteEntry): number;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeRank = computeRank;
|
|
4
|
+
exports.compareEntries = compareEntries;
|
|
5
|
+
/**
|
|
6
|
+
* Cuán específico es cada tipo de segmento. Menor gana.
|
|
7
|
+
*
|
|
8
|
+
* Un literal describe una sola URL; un parámetro, todas las de su posición; un
|
|
9
|
+
* catch-all, todas las que empiecen igual. Ordenar por esto es lo que hace que
|
|
10
|
+
* `/users/nuevo` gane sobre `/users/[id]` sin que nadie declare la prioridad.
|
|
11
|
+
*/
|
|
12
|
+
const KIND_RANK = {
|
|
13
|
+
static: 0,
|
|
14
|
+
dynamic: 1,
|
|
15
|
+
catchAll: 2,
|
|
16
|
+
optionalCatchAll: 3,
|
|
17
|
+
};
|
|
18
|
+
/** Clave de comparación de una ruta: la especificidad de cada segmento, en orden. */
|
|
19
|
+
function computeRank(segments) {
|
|
20
|
+
return segments.map((s) => KIND_RANK[s.kind]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Orden en que se prueban las rutas.
|
|
24
|
+
*
|
|
25
|
+
* Se compara segmento a segmento; el primero que difiere decide. A igual
|
|
26
|
+
* especificidad gana la más larga —describe más de la URL— y, si aún empatan, el
|
|
27
|
+
* orden alfabético del patrón, para que el resultado no dependa de en qué orden
|
|
28
|
+
* el sistema de archivos devolvió los nombres.
|
|
29
|
+
*/
|
|
30
|
+
function compareEntries(a, b) {
|
|
31
|
+
const length = Math.min(a.rank.length, b.rank.length);
|
|
32
|
+
for (let i = 0; i < length; i++) {
|
|
33
|
+
if (a.rank[i] !== b.rank[i])
|
|
34
|
+
return a.rank[i] - b.rank[i];
|
|
35
|
+
}
|
|
36
|
+
if (a.rank.length !== b.rank.length) {
|
|
37
|
+
const [corta, larga] = a.rank.length < b.rank.length ? [a, b] : [b, a];
|
|
38
|
+
const extra = larga.rank[corta.rank.length];
|
|
39
|
+
// Un catch-all opcional puede no consumir nada, así que la ruta que lo lleva
|
|
40
|
+
// atiende también todo lo que atiende la corta: es la más general de las dos.
|
|
41
|
+
// Sin esto, `/users/[id]/[[...rest]]` se probaría antes que `/users/[id]` y
|
|
42
|
+
// esta última quedaría inalcanzable.
|
|
43
|
+
if (extra === KIND_RANK.optionalCatchAll)
|
|
44
|
+
return corta === a ? -1 : 1;
|
|
45
|
+
// En cualquier otro caso, la más larga describe más de la URL.
|
|
46
|
+
return b.rank.length - a.rank.length;
|
|
47
|
+
}
|
|
48
|
+
return a.pattern.localeCompare(b.pattern);
|
|
49
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Router, type RequestHandler } from 'express';
|
|
2
|
+
import { type MiddlewareInfo, type PageModule } from '../helpers/middlewares';
|
|
3
|
+
import { type IsrRuntime } from '../isr/handler';
|
|
4
|
+
import type { ModuleLoader } from '../runtime/module-loader';
|
|
5
|
+
import type { RouteEntry, RouteManifest } from './types';
|
|
6
|
+
export interface ChainContext {
|
|
7
|
+
moduleLoader?: ModuleLoader;
|
|
8
|
+
/**
|
|
9
|
+
* Middlewares que registró el build. Si están, la cadena se arma con ellos y
|
|
10
|
+
* no se busca en el sistema de archivos; `buildRouterState` los toma del
|
|
11
|
+
* manifest.
|
|
12
|
+
*/
|
|
13
|
+
middlewares?: readonly MiddlewareInfo[];
|
|
14
|
+
renderOptions: {
|
|
15
|
+
enablePlatformDetection: boolean;
|
|
16
|
+
renderFunction?: (options: {
|
|
17
|
+
url: string;
|
|
18
|
+
platform: string;
|
|
19
|
+
req: any;
|
|
20
|
+
}) => Promise<string>;
|
|
21
|
+
viteConfig: unknown;
|
|
22
|
+
security: unknown;
|
|
23
|
+
};
|
|
24
|
+
/** Caché incremental. Sin ella, una página `isr` se comporta como `ssr`. */
|
|
25
|
+
isr?: IsrRuntime;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* La cadena de middlewares de una entrada.
|
|
29
|
+
*
|
|
30
|
+
* Es el cuerpo que estaba duplicado entre los dos constructores de router del
|
|
31
|
+
* modelo anterior: lo que hace un middleware de carpeta, `getServerSideProps` y
|
|
32
|
+
* el render, en ese orden. Tenerlo una vez es lo que garantiza que los dos
|
|
33
|
+
* caminos se comporten igual.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildChain(entry: RouteEntry, ctx: ChainContext, preloaded?: PageModule | null): Promise<RequestHandler[]>;
|
|
36
|
+
export interface RessRouterState {
|
|
37
|
+
manifest: RouteManifest;
|
|
38
|
+
chains: Map<string, RequestHandler[]>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Construye el estado de ruteo: el manifest más la cadena de cada entrada.
|
|
42
|
+
*
|
|
43
|
+
* Se arma entero y se devuelve entero, de modo que reemplazarlo en desarrollo
|
|
44
|
+
* sea una sola asignación y ninguna petición vea una mezcla de dos versiones.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildRouterState(manifest: RouteManifest, ctx: ChainContext): Promise<RessRouterState>;
|
|
47
|
+
/**
|
|
48
|
+
* El router de páginas.
|
|
49
|
+
*
|
|
50
|
+
* Un solo despachador para todas las rutas y un solo manejador de errores, que
|
|
51
|
+
* sabe qué ruta falló porque el despachador lo dejó en la petición. El modelo
|
|
52
|
+
* anterior registraba un manejador por página.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createPageRouter(getState: () => RessRouterState, ctx: ChainContext): Router;
|