@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,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildChain = buildChain;
|
|
4
|
+
exports.buildRouterState = buildRouterState;
|
|
5
|
+
exports.createPageRouter = createPageRouter;
|
|
6
|
+
const express_1 = require("express");
|
|
7
|
+
const middlewares_1 = require("../helpers/middlewares");
|
|
8
|
+
const handler_1 = require("../isr/handler");
|
|
9
|
+
const page_config_1 = require("../isr/page-config");
|
|
10
|
+
const render_1 = require("../render");
|
|
11
|
+
const dispatcher_1 = require("./dispatcher");
|
|
12
|
+
const manifest_1 = require("./manifest");
|
|
13
|
+
const types_1 = require("./types");
|
|
14
|
+
/**
|
|
15
|
+
* La cadena de middlewares de una entrada.
|
|
16
|
+
*
|
|
17
|
+
* Es el cuerpo que estaba duplicado entre los dos constructores de router del
|
|
18
|
+
* modelo anterior: lo que hace un middleware de carpeta, `getServerSideProps` y
|
|
19
|
+
* el render, en ese orden. Tenerlo una vez es lo que garantiza que los dos
|
|
20
|
+
* caminos se comporten igual.
|
|
21
|
+
*/
|
|
22
|
+
async function buildChain(entry, ctx, preloaded) {
|
|
23
|
+
const pageModule = preloaded === undefined
|
|
24
|
+
? await (0, middlewares_1.loadPageModule)(entry.sourceFile, ctx.moduleLoader)
|
|
25
|
+
: preloaded;
|
|
26
|
+
// La configuración de render se valida al armar la ruta, no al atender: una
|
|
27
|
+
// página mal configurada se informa al arrancar, nombrando archivo y campo.
|
|
28
|
+
// Esa ruta responde con su error; el resto de la aplicación sigue en pie.
|
|
29
|
+
let rendering;
|
|
30
|
+
try {
|
|
31
|
+
rendering = (0, page_config_1.resolvePageRendering)(pageModule?.pageConfig, entry.sourceFile);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error(error.message);
|
|
35
|
+
return [(_req, _res, next) => next(error)];
|
|
36
|
+
}
|
|
37
|
+
const page = {
|
|
38
|
+
route: entry.pattern,
|
|
39
|
+
file: entry.sourceFile,
|
|
40
|
+
pageId: entry.pageId,
|
|
41
|
+
...(pageModule?.pageConfig ? { config: pageModule.pageConfig } : {}),
|
|
42
|
+
};
|
|
43
|
+
const middlewares = await (0, middlewares_1.loadPageMiddlewares)(middlewareRouteOf(entry), ctx.moduleLoader, ctx.middlewares);
|
|
44
|
+
const render = [
|
|
45
|
+
pageModule ? (0, middlewares_1.createGetServerSidePropsMiddleware)(pageModule, entry.sourceFile) : (_req, _res, next) => next(),
|
|
46
|
+
(0, render_1.createRenderMiddleware)(page, ctx.renderOptions),
|
|
47
|
+
];
|
|
48
|
+
return [
|
|
49
|
+
// Express reasigna `req.params` cada vez que la petición entra a un router
|
|
50
|
+
// anidado, así que los valores que extrajo el despachador se restauran acá,
|
|
51
|
+
// ya dentro de la cadena de la página.
|
|
52
|
+
(req, _res, next) => {
|
|
53
|
+
if (req.ressParams)
|
|
54
|
+
req.params = req.ressParams;
|
|
55
|
+
next();
|
|
56
|
+
},
|
|
57
|
+
// Se busca por la ubicación del archivo y no por el patrón: un grupo no
|
|
58
|
+
// aparece en la URL, así que `app/pages/(marketing)/about.tsx` tiene patrón
|
|
59
|
+
// `/about` y sus middlewares viven en `/(marketing)`. Compararlos daría
|
|
60
|
+
// siempre falso y la carpeta quedaría sin proteger.
|
|
61
|
+
//
|
|
62
|
+
// Corren también en una página ISR, con la petición real y en cada
|
|
63
|
+
// petición: la caché sólo evita obtener datos y renderizar, nunca la
|
|
64
|
+
// protección de la carpeta.
|
|
65
|
+
...middlewares,
|
|
66
|
+
//
|
|
67
|
+
// El manejador va en toda página, no sólo en las que el export estático
|
|
68
|
+
// declara ISR: `getServerSideProps` también puede declararlo, con la misma
|
|
69
|
+
// firma. En una página SSR sólo agrega una comparación.
|
|
70
|
+
...(ctx.isr && !ctx.renderOptions.renderFunction
|
|
71
|
+
? [(0, handler_1.createIsrHandler)({ entry, rendering, render, runtime: ctx.isr, hasMiddlewares: middlewares.length > 0 })]
|
|
72
|
+
: render),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* La ruta con la que se buscan los middlewares de una entrada: la que describe
|
|
77
|
+
* dónde vive el archivo, con los grupos incluidos.
|
|
78
|
+
*/
|
|
79
|
+
function middlewareRouteOf(entry) {
|
|
80
|
+
const relative = entry.sourceFile
|
|
81
|
+
.replace(/\\/g, '/')
|
|
82
|
+
.replace(/^.*app\/pages\//, '')
|
|
83
|
+
.replace(/\.[^./]+$/, '')
|
|
84
|
+
.replace(/\/?index$/, '');
|
|
85
|
+
return '/' + relative;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Construye el estado de ruteo: el manifest más la cadena de cada entrada.
|
|
89
|
+
*
|
|
90
|
+
* Se arma entero y se devuelve entero, de modo que reemplazarlo en desarrollo
|
|
91
|
+
* sea una sola asignación y ninguna petición vea una mezcla de dos versiones.
|
|
92
|
+
*/
|
|
93
|
+
async function buildRouterState(manifest, ctx) {
|
|
94
|
+
const chains = new Map();
|
|
95
|
+
const accepted = [];
|
|
96
|
+
// El manifest del build trae los middlewares; el del escaneo de desarrollo
|
|
97
|
+
// no, y ahí se siguen descubriendo en el sistema de archivos.
|
|
98
|
+
if (manifest.middlewares) {
|
|
99
|
+
ctx = { ...ctx, middlewares: (0, middlewares_1.middlewaresFromManifest)(manifest.middlewares) };
|
|
100
|
+
}
|
|
101
|
+
for (const entry of manifest.entries) {
|
|
102
|
+
if (entry.kind === 'api') {
|
|
103
|
+
accepted.push(entry);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Un renderer personalizado produce el HTML sin usar el componente de la
|
|
107
|
+
// página: en esa API la ruta puede tener sólo exports auxiliares.
|
|
108
|
+
const pageModule = ctx.renderOptions.renderFunction
|
|
109
|
+
? undefined
|
|
110
|
+
: await (0, middlewares_1.loadPageModule)(entry.sourceFile, ctx.moduleLoader);
|
|
111
|
+
if (!ctx.renderOptions.renderFunction && !pageModule?.default) {
|
|
112
|
+
console.warn(`[ress] "${entry.sourceFile}" es ambiguo: tiene una extensión de página, ` +
|
|
113
|
+
(pageModule
|
|
114
|
+
? 'pero no exporta un componente por defecto ni coincide con una exclusión conocida. '
|
|
115
|
+
: 'pero su módulo no pudo cargarse para validar el componente por defecto. ') +
|
|
116
|
+
'No se registrará como ruta; renombralo con el prefijo "_" si es un archivo auxiliar.');
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
accepted.push(entry);
|
|
120
|
+
chains.set(entry.pageId, await buildChain(entry, ctx, pageModule));
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
manifest: accepted.length === manifest.entries.length
|
|
124
|
+
? manifest
|
|
125
|
+
: (0, manifest_1.createRouteManifest)({
|
|
126
|
+
version: types_1.ROUTE_MANIFEST_VERSION,
|
|
127
|
+
entries: accepted,
|
|
128
|
+
middlewares: manifest.middlewares ? [...manifest.middlewares] : undefined,
|
|
129
|
+
}),
|
|
130
|
+
chains,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* El router de páginas.
|
|
135
|
+
*
|
|
136
|
+
* Un solo despachador para todas las rutas y un solo manejador de errores, que
|
|
137
|
+
* sabe qué ruta falló porque el despachador lo dejó en la petición. El modelo
|
|
138
|
+
* anterior registraba un manejador por página.
|
|
139
|
+
*/
|
|
140
|
+
function createPageRouter(getState, ctx) {
|
|
141
|
+
const router = (0, express_1.Router)();
|
|
142
|
+
router.use((0, dispatcher_1.createDispatcher)(getState));
|
|
143
|
+
router.use((err, req, res, next) => {
|
|
144
|
+
const entry = req.ressRoute;
|
|
145
|
+
if (!entry)
|
|
146
|
+
return next(err);
|
|
147
|
+
// El mismo origen de middlewares que la cadena: los del build si están.
|
|
148
|
+
const registered = getState().manifest.middlewares;
|
|
149
|
+
const middlewares = registered ? (0, middlewares_1.middlewaresFromManifest)(registered) : undefined;
|
|
150
|
+
return (0, middlewares_1.createPageErrorHandler)(entry.sourceFile, ctx.moduleLoader, middlewares)(err, req, res, next);
|
|
151
|
+
});
|
|
152
|
+
return router;
|
|
153
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { type RouteManifestData } from './types';
|
|
3
|
+
export interface ScanOptions {
|
|
4
|
+
pagesDir?: string;
|
|
5
|
+
/** Raíz del proyecto. Los paths persistidos en el manifest son relativos a ella. */
|
|
6
|
+
root?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Un conflicto en build detiene el proceso; en desarrollo avisa y sigue, para
|
|
9
|
+
* no dejar el servidor caído mientras alguien renombra un archivo.
|
|
10
|
+
*/
|
|
11
|
+
strict?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Qué rutas existen en el proyecto.
|
|
15
|
+
*
|
|
16
|
+
* Lee el sistema de archivos **una vez**. El modelo anterior lo globeaba en cada
|
|
17
|
+
* arranque, también en producción, donde el árbol ya no puede cambiar.
|
|
18
|
+
*/
|
|
19
|
+
export declare function scanRoutes(options?: ScanOptions): Promise<RouteManifestData>;
|
|
20
|
+
/**
|
|
21
|
+
* Convierte cualquier resultado del glob a una identidad portable del proyecto.
|
|
22
|
+
* El manifest se publica dentro de `dist`, por lo que persistir el path absoluto
|
|
23
|
+
* filtraría la ubicación de la máquina que hizo el build y volvería el artefacto
|
|
24
|
+
* imposible de mover.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toProjectRelativePath(file: string, root: string, pathApi?: Pick<typeof path, 'resolve' | 'relative' | 'isAbsolute' | 'sep'>): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
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.scanRoutes = scanRoutes;
|
|
7
|
+
exports.toProjectRelativePath = toProjectRelativePath;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
10
|
+
const module_extensions_1 = require("../fs/module-extensions");
|
|
11
|
+
const parse_1 = require("./parse");
|
|
12
|
+
const rank_1 = require("./rank");
|
|
13
|
+
const rank_2 = require("./rank");
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
/**
|
|
16
|
+
* Qué rutas existen en el proyecto.
|
|
17
|
+
*
|
|
18
|
+
* Lee el sistema de archivos **una vez**. El modelo anterior lo globeaba en cada
|
|
19
|
+
* arranque, también en producción, donde el árbol ya no puede cambiar.
|
|
20
|
+
*/
|
|
21
|
+
async function scanRoutes(options = {}) {
|
|
22
|
+
const pagesDir = options.pagesDir ?? 'app/pages';
|
|
23
|
+
const root = resolveProjectRoot(pagesDir, options.root);
|
|
24
|
+
const policy = (0, module_extensions_1.getExtensionPolicy)();
|
|
25
|
+
// Qué extensiones cuentan lo declara la política del proyecto, no este archivo.
|
|
26
|
+
const files = await (0, fast_glob_1.default)((0, module_extensions_1.pageGlob)(policy, pagesDir), { absolute: false });
|
|
27
|
+
const entries = [];
|
|
28
|
+
const byPattern = new Map();
|
|
29
|
+
const conflicts = [];
|
|
30
|
+
for (const file of files.sort()) {
|
|
31
|
+
// fast-glob devuelve separadores nativos en Windows; el parser de rutas y
|
|
32
|
+
// los identificadores del catálogo usan `/` para que el resultado sea
|
|
33
|
+
// idéntico en todos los sistemas.
|
|
34
|
+
const normalizedFile = file.replace(/\\/g, '/');
|
|
35
|
+
const sourceFile = toProjectRelativePath(normalizedFile, root);
|
|
36
|
+
const normalizedPagesDir = pagesDir.replace(/\\/g, '/');
|
|
37
|
+
if (!(0, module_extensions_1.isPageFile)(normalizedFile, policy, normalizedPagesDir))
|
|
38
|
+
continue;
|
|
39
|
+
const parsed = (0, parse_1.parseRoutePattern)(normalizedFile, normalizedPagesDir);
|
|
40
|
+
const entry = {
|
|
41
|
+
pageId: (0, parse_1.pageIdFromFile)(normalizedFile, normalizedPagesDir),
|
|
42
|
+
pattern: parsed.pattern,
|
|
43
|
+
segments: parsed.segments,
|
|
44
|
+
kind: (0, parse_1.classifyEntry)(normalizedFile, normalizedPagesDir),
|
|
45
|
+
sourceFile,
|
|
46
|
+
paramNames: parsed.paramNames,
|
|
47
|
+
groups: parsed.groups,
|
|
48
|
+
rank: (0, rank_1.computeRank)(parsed.segments),
|
|
49
|
+
server: { devId: '/' + sourceFile },
|
|
50
|
+
};
|
|
51
|
+
// Dos archivos que producen la misma URL son ambiguos: cuál gana dependería
|
|
52
|
+
// del orden en que el sistema de archivos los devolvió.
|
|
53
|
+
const clash = byPattern.get(`${entry.kind} ${entry.pattern}`);
|
|
54
|
+
if (clash) {
|
|
55
|
+
const message = `[ress] "${clash.sourceFile}" y "${sourceFile}" producen la misma ruta "${entry.pattern}". ` +
|
|
56
|
+
'Renombrá uno de los dos.';
|
|
57
|
+
if (options.strict)
|
|
58
|
+
throw new Error(message);
|
|
59
|
+
console.warn(`${message}\nPor ahora responde "${clash.sourceFile}".`);
|
|
60
|
+
conflicts.push({ pattern: entry.pattern, files: [clash.sourceFile, sourceFile] });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
byPattern.set(`${entry.kind} ${entry.pattern}`, entry);
|
|
64
|
+
entries.push(entry);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
version: types_1.ROUTE_MANIFEST_VERSION,
|
|
68
|
+
entries: entries.sort(rank_2.compareEntries),
|
|
69
|
+
...(conflicts.length ? { conflicts } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Convierte cualquier resultado del glob a una identidad portable del proyecto.
|
|
74
|
+
* El manifest se publica dentro de `dist`, por lo que persistir el path absoluto
|
|
75
|
+
* filtraría la ubicación de la máquina que hizo el build y volvería el artefacto
|
|
76
|
+
* imposible de mover.
|
|
77
|
+
*/
|
|
78
|
+
function toProjectRelativePath(file, root, pathApi = node_path_1.default) {
|
|
79
|
+
const absolute = pathApi.resolve(file);
|
|
80
|
+
const relative = pathApi.relative(root, absolute).replace(/\\/g, '/');
|
|
81
|
+
if (relative === '..' || relative.startsWith('../') || pathApi.isAbsolute(relative)) {
|
|
82
|
+
throw new Error(`[ress] La ruta "${file}" queda fuera de la raíz del proyecto "${root}". ` +
|
|
83
|
+
'Las entradas del manifest deben pertenecer al proyecto.');
|
|
84
|
+
}
|
|
85
|
+
return relative;
|
|
86
|
+
}
|
|
87
|
+
function resolveProjectRoot(pagesDir, configuredRoot) {
|
|
88
|
+
if (configuredRoot)
|
|
89
|
+
return node_path_1.default.resolve(configuredRoot);
|
|
90
|
+
if (!node_path_1.default.isAbsolute(pagesDir))
|
|
91
|
+
return process.cwd();
|
|
92
|
+
// Compatibilidad con quienes ya pasan `/proyecto/app/pages` sin `root`.
|
|
93
|
+
// Para un directorio distinto, la raíz debe declararse de forma explícita.
|
|
94
|
+
const normalized = pagesDir.replace(/\\/g, '/').replace(/\/$/, '');
|
|
95
|
+
if (normalized.endsWith('/app/pages'))
|
|
96
|
+
return node_path_1.default.resolve(pagesDir, '../..');
|
|
97
|
+
return node_path_1.default.dirname(node_path_1.default.resolve(pagesDir));
|
|
98
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué rutas existen y cuál atiende cada URL.
|
|
3
|
+
*
|
|
4
|
+
* El modelo anterior derivaba la ruta del archivo en cada arranque, globeando el
|
|
5
|
+
* sistema de archivos también en producción, y traducía el patrón a una cadena
|
|
6
|
+
* de Express que no podía expresar ni catch-all ni grupos. Acá el conjunto de
|
|
7
|
+
* rutas es un dato: se calcula una vez, se serializa en el build y se carga.
|
|
8
|
+
*/
|
|
9
|
+
export type SegmentKind = 'static' | 'dynamic' | 'catchAll' | 'optionalCatchAll';
|
|
10
|
+
export interface RouteSegment {
|
|
11
|
+
kind: SegmentKind;
|
|
12
|
+
/** Literal de URL para `static`; nombre del parámetro para el resto. */
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
/** Qué es una entrada. Determina si participa del matcheo de navegación. */
|
|
16
|
+
export type RouteEntryKind = 'page' | 'error' | 'api';
|
|
17
|
+
/** Identificador estable de una página. La misma clave que usa el catálogo de variantes. */
|
|
18
|
+
export type PageId = string;
|
|
19
|
+
export interface ServerModuleRef {
|
|
20
|
+
/** Identificador que entiende el cargador de módulos en desarrollo. */
|
|
21
|
+
devId: string;
|
|
22
|
+
/**
|
|
23
|
+
* Chunk compilado, relativo a la raíz de salida del build de servidor. Lo
|
|
24
|
+
* completa el build; en desarrollo no existe.
|
|
25
|
+
*/
|
|
26
|
+
buildChunk?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RouteEntry {
|
|
29
|
+
pageId: PageId;
|
|
30
|
+
/** Patrón canónico con notación de archivos. Ej: `/users/[id]`. */
|
|
31
|
+
pattern: string;
|
|
32
|
+
segments: RouteSegment[];
|
|
33
|
+
kind: RouteEntryKind;
|
|
34
|
+
/** Archivo fuente, relativo a la raíz del proyecto. */
|
|
35
|
+
sourceFile: string;
|
|
36
|
+
/** Nombres de parámetro, en orden de aparición. */
|
|
37
|
+
paramNames: string[];
|
|
38
|
+
/** Grupos atravesados. Informativo: no afectan la URL. */
|
|
39
|
+
groups: string[];
|
|
40
|
+
/** Clave de precedencia precalculada. */
|
|
41
|
+
rank: number[];
|
|
42
|
+
server: ServerModuleRef;
|
|
43
|
+
/** Sólo para `error`: qué código de estado atiende. */
|
|
44
|
+
statusCode?: number;
|
|
45
|
+
/** Sólo para `api`: métodos declarados. Ausente significa todos. */
|
|
46
|
+
methods?: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Un middleware registrado por el build.
|
|
50
|
+
*
|
|
51
|
+
* Producción los lee de acá y no del sistema de archivos: un despliegue que
|
|
52
|
+
* copia sólo `dist/` no tiene `app/pages`, y globear ahí devolvía vacío, así que
|
|
53
|
+
* todas las rutas quedaban sin sus middlewares —autenticación incluida—.
|
|
54
|
+
*/
|
|
55
|
+
export interface MiddlewareEntry {
|
|
56
|
+
/** Archivo fuente, relativo a la raíz del proyecto. */
|
|
57
|
+
file: string;
|
|
58
|
+
/** Carpeta (o página, si es `page-specific`) a la que aplica. */
|
|
59
|
+
route: string;
|
|
60
|
+
type: 'page-specific' | 'folder-global';
|
|
61
|
+
/** Nivel de carpeta, para ordenar la cadena por jerarquía. */
|
|
62
|
+
depth: number;
|
|
63
|
+
server: ServerModuleRef;
|
|
64
|
+
}
|
|
65
|
+
export interface RouteMatch {
|
|
66
|
+
entry: RouteEntry;
|
|
67
|
+
params: Record<string, string | string[]>;
|
|
68
|
+
}
|
|
69
|
+
export interface MatchOptions {
|
|
70
|
+
/** Qué clases de entrada participan. Por defecto sólo `page`. */
|
|
71
|
+
kinds?: readonly RouteEntryKind[];
|
|
72
|
+
}
|
|
73
|
+
/** Dos archivos que compiten por el mismo patrón, detectados al escanear. */
|
|
74
|
+
export interface RouteConflict {
|
|
75
|
+
pattern: string;
|
|
76
|
+
files: string[];
|
|
77
|
+
}
|
|
78
|
+
/** Lo que se serializa al disco. */
|
|
79
|
+
export interface RouteManifestData {
|
|
80
|
+
/** Sube cuando el formato cambia de forma incompatible. */
|
|
81
|
+
version: number;
|
|
82
|
+
entries: RouteEntry[];
|
|
83
|
+
/**
|
|
84
|
+
* Conflictos detectados en modo no estricto (desarrollo).
|
|
85
|
+
*
|
|
86
|
+
* En modo estricto (`build`) `scanRoutes` lanza en vez de completar esta
|
|
87
|
+
* lista: un build no deja artefactos parciales publicables. F-033 (`ress
|
|
88
|
+
* info`) es quien la consume para diagnosticar sin abortar.
|
|
89
|
+
*/
|
|
90
|
+
conflicts?: RouteConflict[];
|
|
91
|
+
/**
|
|
92
|
+
* Middlewares del proyecto. Obligatorio en un manifest emitido por el build;
|
|
93
|
+
* ausente en el que arma el escaneo de desarrollo, que los sigue descubriendo
|
|
94
|
+
* en el sistema de archivos.
|
|
95
|
+
*/
|
|
96
|
+
middlewares?: MiddlewareEntry[];
|
|
97
|
+
}
|
|
98
|
+
export interface RouteManifest {
|
|
99
|
+
readonly entries: readonly RouteEntry[];
|
|
100
|
+
/** Middlewares registrados por el build. `undefined` en desarrollo. */
|
|
101
|
+
readonly middlewares?: readonly MiddlewareEntry[];
|
|
102
|
+
/** Qué entrada atiende esta URL. `undefined` si ninguna. */
|
|
103
|
+
match(pathname: string, options?: MatchOptions): RouteMatch | undefined;
|
|
104
|
+
/** Una entrada por su identificador. */
|
|
105
|
+
get(pageId: PageId): RouteEntry | undefined;
|
|
106
|
+
/** Las entradas de una clase, en orden de precedencia. */
|
|
107
|
+
byKind(kind: RouteEntryKind): readonly RouteEntry[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Sube cuando el formato del manifest cambia de forma incompatible.
|
|
111
|
+
*
|
|
112
|
+
* 2: el build registra los middlewares con su chunk (F-040 PR-4).
|
|
113
|
+
*/
|
|
114
|
+
export declare const ROUTE_MANIFEST_VERSION = 2;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Qué rutas existen y cuál atiende cada URL.
|
|
4
|
+
*
|
|
5
|
+
* El modelo anterior derivaba la ruta del archivo en cada arranque, globeando el
|
|
6
|
+
* sistema de archivos también en producción, y traducía el patrón a una cadena
|
|
7
|
+
* de Express que no podía expresar ni catch-all ni grupos. Acá el conjunto de
|
|
8
|
+
* rutas es un dato: se calcula una vez, se serializa en el build y se carga.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ROUTE_MANIFEST_VERSION = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* Sube cuando el formato del manifest cambia de forma incompatible.
|
|
14
|
+
*
|
|
15
|
+
* 2: el build registra los middlewares con su chunk (F-040 PR-4).
|
|
16
|
+
*/
|
|
17
|
+
exports.ROUTE_MANIFEST_VERSION = 2;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime de desarrollo.
|
|
3
|
+
*
|
|
4
|
+
* Crea el servidor de Vite, resuelve el entorno de servidor y construye el cargador de
|
|
5
|
+
* módulos una sola vez, que es el que usa el resto del framework.
|
|
6
|
+
*/
|
|
7
|
+
import { type FetchableEnvironment, type ModuleLoader } from './module-loader';
|
|
8
|
+
import type { AssetIndex, VariantCatalog } from '../variants/types';
|
|
9
|
+
import type { RouteManifest } from '../routes/types';
|
|
10
|
+
export interface RessRuntime {
|
|
11
|
+
/** Instancia de Vite. `null` en producción. */
|
|
12
|
+
vite: any | null;
|
|
13
|
+
/** Carga módulos de usuario. Único en dev y prod. */
|
|
14
|
+
moduleLoader: ModuleLoader;
|
|
15
|
+
templateHtml: string;
|
|
16
|
+
isProduction: boolean;
|
|
17
|
+
/** Qué variantes tiene cada página. Se arma una vez al arrancar. */
|
|
18
|
+
catalog: VariantCatalog;
|
|
19
|
+
/** Qué rutas existen y cuál atiende cada URL. */
|
|
20
|
+
routes: RouteManifest;
|
|
21
|
+
/** Artefactos por página y variante. */
|
|
22
|
+
assets: AssetIndex;
|
|
23
|
+
/** Archivos que emitió el build de cliente. Vacío en desarrollo. */
|
|
24
|
+
buildOutputs: ReadonlySet<string>;
|
|
25
|
+
/** Identificador del build en producción. Ausente en desarrollo. */
|
|
26
|
+
buildId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Registra un aviso de cambio en el código de usuario. Devuelve la función
|
|
29
|
+
* para dejar de escuchar. En producción no se invoca nunca.
|
|
30
|
+
*/
|
|
31
|
+
onSourceChange(cb: (file: string) => void): () => void;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Elige el entorno desde el que se cargarán los módulos de usuario.
|
|
36
|
+
*
|
|
37
|
+
* Se prefiere `ssr` por nombre; si un proyecto declaró otros entornos, se cae al
|
|
38
|
+
* primero disponible en vez de fallar. Nunca se asume que el entorno sea del
|
|
39
|
+
* tipo ejecutable: el cargador se construye sobre `fetchModule()`, presente en
|
|
40
|
+
* todos los tipos.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveSsrEnvironment(server: any): FetchableEnvironment;
|
|
43
|
+
export interface DevRuntimeOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Plugins de Vite a aplicar, además de los que el proyecto declare en su
|
|
46
|
+
* propio `vite.config.*` si lo tiene.
|
|
47
|
+
*
|
|
48
|
+
* F-033 (la CLI) los necesita: un proyecto sin `vite.config.ts` no tiene
|
|
49
|
+
* quién le pase `ressPlugin()` a Vite si `createDevRuntime` no acepta
|
|
50
|
+
* plugins inline. Antes de esta opción, `createServer` dependía por completo
|
|
51
|
+
* de que Vite encontrara un archivo de configuración en el directorio de
|
|
52
|
+
* trabajo — que es justamente lo que la CLI existe para volver innecesario.
|
|
53
|
+
*/
|
|
54
|
+
plugins?: unknown[];
|
|
55
|
+
}
|
|
56
|
+
export declare function createDevRuntime(base?: string,
|
|
57
|
+
/**
|
|
58
|
+
* Prefijo con el que se emiten las URL de artefactos.
|
|
59
|
+
*
|
|
60
|
+
* Va aparte del `base` de Vite porque el prefijo de ruta lo administra el
|
|
61
|
+
* framework: el pipeline de petición lo quita antes de que Vite vea la URL, así
|
|
62
|
+
* que Vite sigue viviendo en `/` y sólo lo que se escribe en el HTML lo lleva.
|
|
63
|
+
*/
|
|
64
|
+
assetPrefix?: string, options?: DevRuntimeOptions): Promise<RessRuntime>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime de desarrollo.
|
|
4
|
+
*
|
|
5
|
+
* Crea el servidor de Vite, resuelve el entorno de servidor y construye el cargador de
|
|
6
|
+
* módulos una sola vez, que es el que usa el resto del framework.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.resolveSsrEnvironment = resolveSsrEnvironment;
|
|
10
|
+
exports.createDevRuntime = createDevRuntime;
|
|
11
|
+
const module_loader_1 = require("./module-loader");
|
|
12
|
+
const assets_1 = require("../variants/assets");
|
|
13
|
+
const manifest_1 = require("../routes/manifest");
|
|
14
|
+
const scan_1 = require("../routes/scan");
|
|
15
|
+
const index_1 = require("../plugin/index");
|
|
16
|
+
const template_1 = require("./template");
|
|
17
|
+
/**
|
|
18
|
+
* Elige el entorno desde el que se cargarán los módulos de usuario.
|
|
19
|
+
*
|
|
20
|
+
* Se prefiere `ssr` por nombre; si un proyecto declaró otros entornos, se cae al
|
|
21
|
+
* primero disponible en vez de fallar. Nunca se asume que el entorno sea del
|
|
22
|
+
* tipo ejecutable: el cargador se construye sobre `fetchModule()`, presente en
|
|
23
|
+
* todos los tipos.
|
|
24
|
+
*/
|
|
25
|
+
function resolveSsrEnvironment(server) {
|
|
26
|
+
const envs = server?.environments;
|
|
27
|
+
if (!envs) {
|
|
28
|
+
throw new Error('[ress] El servidor de Vite no expone environments. ' +
|
|
29
|
+
'ress.js requiere Vite 8 o superior.');
|
|
30
|
+
}
|
|
31
|
+
const env = envs.ssr ?? Object.values(envs)[0];
|
|
32
|
+
if (!env || typeof env.fetchModule !== 'function') {
|
|
33
|
+
throw new Error('[ress] No se encontró un entorno de servidor utilizable. ' +
|
|
34
|
+
`Entornos disponibles: ${Object.keys(envs).join(', ') || '(ninguno)'}`);
|
|
35
|
+
}
|
|
36
|
+
return env;
|
|
37
|
+
}
|
|
38
|
+
async function createDevRuntime(base = '/',
|
|
39
|
+
/**
|
|
40
|
+
* Prefijo con el que se emiten las URL de artefactos.
|
|
41
|
+
*
|
|
42
|
+
* Va aparte del `base` de Vite porque el prefijo de ruta lo administra el
|
|
43
|
+
* framework: el pipeline de petición lo quita antes de que Vite vea la URL, así
|
|
44
|
+
* que Vite sigue viviendo en `/` y sólo lo que se escribe en el HTML lo lleva.
|
|
45
|
+
*/
|
|
46
|
+
assetPrefix = base, options = {}) {
|
|
47
|
+
const { createServer } = await import('vite');
|
|
48
|
+
const vite = await createServer({
|
|
49
|
+
server: { middlewareMode: true },
|
|
50
|
+
appType: 'custom',
|
|
51
|
+
base,
|
|
52
|
+
plugins: options.plugins,
|
|
53
|
+
});
|
|
54
|
+
const env = resolveSsrEnvironment(vite);
|
|
55
|
+
const moduleLoader = await (0, module_loader_1.createDevModuleLoader)(env);
|
|
56
|
+
// El runner mantiene su propia caché de módulos evaluados y corre sin canal de
|
|
57
|
+
// HMR, así que nada la descarta por su cuenta. Conectar el watcher es lo que
|
|
58
|
+
// hace que editar una página o un middleware se refleje en la siguiente
|
|
59
|
+
// petición en vez de exigir reiniciar el servidor.
|
|
60
|
+
const listeners = new Set();
|
|
61
|
+
const onFileChange = (file) => {
|
|
62
|
+
moduleLoader.invalidate(file);
|
|
63
|
+
for (const cb of listeners)
|
|
64
|
+
cb(file);
|
|
65
|
+
};
|
|
66
|
+
vite.watcher.on('change', onFileChange);
|
|
67
|
+
vite.watcher.on('unlink', onFileChange);
|
|
68
|
+
const templateHtml = await (0, template_1.readTemplateHtml)();
|
|
69
|
+
// El plugin lo arma al resolver la configuración; si por algún motivo no
|
|
70
|
+
// corrió, se arma acá para no dejar el runtime sin catálogo.
|
|
71
|
+
const catalog = (0, index_1.getVariantCatalog)() ?? (await (0, index_1.refreshVariantCatalog)());
|
|
72
|
+
const routes = (0, manifest_1.createRouteManifest)(await (0, scan_1.scanRoutes)());
|
|
73
|
+
return {
|
|
74
|
+
vite,
|
|
75
|
+
moduleLoader,
|
|
76
|
+
templateHtml,
|
|
77
|
+
isProduction: false,
|
|
78
|
+
catalog,
|
|
79
|
+
routes,
|
|
80
|
+
assets: (0, assets_1.createDevAssetIndex)(catalog, assetPrefix),
|
|
81
|
+
// En desarrollo los sirve Vite, no el middleware de estáticos.
|
|
82
|
+
buildOutputs: new Set(),
|
|
83
|
+
onSourceChange: (cb) => {
|
|
84
|
+
listeners.add(cb);
|
|
85
|
+
return () => listeners.delete(cb);
|
|
86
|
+
},
|
|
87
|
+
close: async () => {
|
|
88
|
+
vite.watcher.off('change', onFileChange);
|
|
89
|
+
vite.watcher.off('unlink', onFileChange);
|
|
90
|
+
await moduleLoader.close();
|
|
91
|
+
await vite.close();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** La URL con la que Vite sirve un archivo de estilos como hoja CSS, no como módulo. */
|
|
2
|
+
export declare function directStyleUrl(prefix: string, url: string): string;
|
|
3
|
+
/** Un archivo del proyecto como URL del servidor de desarrollo. */
|
|
4
|
+
export declare function projectFileUrl(file: string, root?: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Las hojas de estilo que importa una página en desarrollo.
|
|
7
|
+
*
|
|
8
|
+
* En desarrollo Vite inyecta el CSS desde el módulo de entrada; una página que
|
|
9
|
+
* no hidrata no carga ese módulo y quedaría sin estilos. Se recorre el grafo del
|
|
10
|
+
* entorno de servidor —que ya cargó la página para renderizarla— y se enlaza
|
|
11
|
+
* cada hoja directamente. En producción el manifest ya las lista.
|
|
12
|
+
*/
|
|
13
|
+
export declare function devModuleStyles(vite: unknown, pageFile: string | undefined, prefix: string): string[];
|
|
@@ -0,0 +1,50 @@
|
|
|
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.directStyleUrl = directStyleUrl;
|
|
7
|
+
exports.projectFileUrl = projectFileUrl;
|
|
8
|
+
exports.devModuleStyles = devModuleStyles;
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const CSS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
11
|
+
const NOT_A_STYLESHEET_RE = /[?&](inline|url|raw)\b/;
|
|
12
|
+
/** La URL con la que Vite sirve un archivo de estilos como hoja CSS, no como módulo. */
|
|
13
|
+
function directStyleUrl(prefix, url) {
|
|
14
|
+
const joined = `${prefix}/${url.replace(/^\/+/, '')}`;
|
|
15
|
+
return `${joined}${joined.includes('?') ? '&' : '?'}direct`;
|
|
16
|
+
}
|
|
17
|
+
/** Un archivo del proyecto como URL del servidor de desarrollo. */
|
|
18
|
+
function projectFileUrl(file, root = process.cwd()) {
|
|
19
|
+
return '/' + node_path_1.default.relative(root, node_path_1.default.resolve(root, file)).replace(/\\/g, '/');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Las hojas de estilo que importa una página en desarrollo.
|
|
23
|
+
*
|
|
24
|
+
* En desarrollo Vite inyecta el CSS desde el módulo de entrada; una página que
|
|
25
|
+
* no hidrata no carga ese módulo y quedaría sin estilos. Se recorre el grafo del
|
|
26
|
+
* entorno de servidor —que ya cargó la página para renderizarla— y se enlaza
|
|
27
|
+
* cada hoja directamente. En producción el manifest ya las lista.
|
|
28
|
+
*/
|
|
29
|
+
function devModuleStyles(vite, pageFile, prefix) {
|
|
30
|
+
const environments = vite?.environments;
|
|
31
|
+
const graph = (environments?.ssr ?? Object.values(environments ?? {})[0])?.moduleGraph;
|
|
32
|
+
if (!graph || !pageFile)
|
|
33
|
+
return [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const styles = [];
|
|
36
|
+
const visit = (mod) => {
|
|
37
|
+
if (seen.has(mod))
|
|
38
|
+
return;
|
|
39
|
+
seen.add(mod);
|
|
40
|
+
if (CSS_RE.test(mod.url) && !NOT_A_STYLESHEET_RE.test(mod.url))
|
|
41
|
+
styles.push(directStyleUrl(prefix, mod.url));
|
|
42
|
+
for (const dep of mod.importedModules)
|
|
43
|
+
visit(dep);
|
|
44
|
+
};
|
|
45
|
+
// Vite guarda los archivos con barras normales, también en Windows:
|
|
46
|
+
// `path.resolve` devuelve `D:\…` y no encontraría la página en el grafo.
|
|
47
|
+
for (const mod of graph.getModulesByFile(node_path_1.default.resolve(pageFile).replace(/\\/g, '/')) ?? [])
|
|
48
|
+
visit(mod);
|
|
49
|
+
return styles;
|
|
50
|
+
}
|