@ressjs/vite-router 0.5.0 → 0.6.0-experimental.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/README.md +41 -31
- 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 +23 -0
- package/dist/config/codegen.js +118 -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 +44 -0
- package/dist/config/load.d.ts +26 -0
- package/dist/config/load.js +73 -0
- package/dist/config/middleware.d.ts +18 -0
- package/dist/config/middleware.js +79 -0
- package/dist/config/rules.d.ts +48 -0
- package/dist/config/rules.js +149 -0
- package/dist/config/types.d.ts +164 -0
- package/dist/config/types.js +2 -0
- package/dist/config/validate.d.ts +19 -0
- package/dist/config/validate.js +105 -0
- package/dist/config/watch.d.ts +41 -0
- package/dist/config/watch.js +132 -0
- package/dist/fs/module-extensions.d.ts +82 -0
- package/dist/fs/module-extensions.js +196 -0
- package/dist/helpers/html-generator.d.ts +22 -5
- package/dist/helpers/html-generator.js +89 -120
- package/dist/helpers/middlewares.d.ts +7 -7
- package/dist/helpers/middlewares.js +51 -103
- package/dist/helpers/request-handler.d.ts +2 -2
- package/dist/helpers/request-handler.js +118 -25
- package/dist/index.d.ts +30 -3
- package/dist/index.js +70 -8
- 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 +66 -0
- package/dist/plugin/environments.js +72 -0
- package/dist/plugin/index.d.ts +54 -0
- package/dist/plugin/index.js +178 -0
- package/dist/plugin/virtual-entries.d.ts +26 -0
- package/dist/plugin/virtual-entries.js +80 -0
- package/dist/render.d.ts +23 -2
- package/dist/render.js +40 -53
- package/dist/router.d.ts +32 -13
- package/dist/router.js +147 -146
- package/dist/routes/dispatcher.d.ts +37 -0
- package/dist/routes/dispatcher.js +69 -0
- package/dist/routes/manifest.d.ts +17 -0
- package/dist/routes/manifest.js +56 -0
- package/dist/routes/match.d.ts +20 -0
- package/dist/routes/match.js +75 -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 +44 -0
- package/dist/routes/router.js +81 -0
- package/dist/routes/scan.d.ts +16 -0
- package/dist/routes/scan.js +82 -0
- package/dist/routes/types.d.ts +85 -0
- package/dist/routes/types.js +13 -0
- package/dist/runtime/dev-server.d.ts +62 -0
- package/dist/runtime/dev-server.js +94 -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 +42 -0
- package/dist/runtime/prod-server.js +142 -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 +50 -0
- package/dist/security/config.d.ts +130 -0
- package/dist/security/config.js +73 -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 +111 -0
- package/dist/security/headers.d.ts +62 -0
- package/dist/security/headers.js +187 -0
- package/dist/security/index.d.ts +24 -0
- package/dist/security/index.js +47 -0
- package/dist/security/serialize.d.ts +48 -0
- package/dist/security/serialize.js +146 -0
- package/dist/variants/assets.d.ts +23 -0
- package/dist/variants/assets.js +78 -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 +69 -0
- package/dist/variants/types.js +2 -0
- package/package.json +22 -10
- package/dist/helpers/vite-config.d.ts +0 -10
- package/dist/helpers/vite-config.js +0 -90
package/dist/router.js
CHANGED
|
@@ -1,161 +1,162 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.getPages = void 0;
|
|
7
|
-
exports.
|
|
4
|
+
exports.waitForViteRouter = waitForViteRouter;
|
|
8
5
|
exports.createViteRouter = createViteRouter;
|
|
9
|
-
exports.listDetectedPages = listDetectedPages;
|
|
10
6
|
const express_1 = require("express");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!isProduction) {
|
|
22
|
-
app.use(viteConfig.vite.middlewares);
|
|
23
|
-
app.use('/dist/.entries', express_2.default.static('dist/.entries'));
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
const prodFactory = (0, vite_config_1.createProdMiddleware)();
|
|
27
|
-
const prodMiddleware = await prodFactory();
|
|
28
|
-
app.use(prodMiddleware);
|
|
29
|
-
}
|
|
30
|
-
const viteRouter = createViteRouter({
|
|
31
|
-
enablePlatformDetection,
|
|
32
|
-
vite: viteConfig.vite,
|
|
33
|
-
templateHtml: viteConfig.templateHtml,
|
|
34
|
-
isProduction
|
|
35
|
-
});
|
|
36
|
-
app.use('/', viteRouter);
|
|
37
|
-
app.use(express_2.default.static('public'));
|
|
38
|
-
const start = () => {
|
|
39
|
-
return app.listen(port, () => {
|
|
40
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
41
|
-
const pages = (0, pages_1.getPages)();
|
|
42
|
-
console.log(`🚀 Auto Vite Server started at http://localhost:${port}`);
|
|
43
|
-
console.log(`📄 Auto-detected routes:`);
|
|
44
|
-
pages.forEach(page => {
|
|
45
|
-
const expressDisplay = page.route !== page.expressRoute ? ` (Express: ${page.expressRoute})` : '';
|
|
46
|
-
console.log(` ${page.route}${expressDisplay} -> ${page.file}`);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
return { app, vite: viteConfig.vite, start };
|
|
7
|
+
const prod_server_1 = require("./runtime/prod-server");
|
|
8
|
+
const security_1 = require("./security");
|
|
9
|
+
const router_1 = require("./routes/router");
|
|
10
|
+
const manifest_1 = require("./routes/manifest");
|
|
11
|
+
const scan_1 = require("./routes/scan");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const routerReadiness = new WeakMap();
|
|
14
|
+
/** Espera a que el runtime, el manifest y las cadenas de middleware estén listos. */
|
|
15
|
+
function waitForViteRouter(router) {
|
|
16
|
+
return routerReadiness.get(router) ?? Promise.resolve();
|
|
52
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* El router de ress.js.
|
|
20
|
+
*
|
|
21
|
+
* Antes había dos constructores con el 90 % del cuerpo repetido, uno para quien
|
|
22
|
+
* deja que el framework arme el runtime y otro para quien lo aporta. La
|
|
23
|
+
* diferencia real entre los dos es de dónde sale el runtime, así que eso es lo
|
|
24
|
+
* único que se decide acá; el resto del camino es uno solo.
|
|
25
|
+
*/
|
|
53
26
|
function createViteRouter(options = {}) {
|
|
54
|
-
const needsAutoConfig = !options.vite && !options.templateHtml && options.isProduction !== true;
|
|
55
|
-
if (needsAutoConfig) {
|
|
56
|
-
return createAutoRouter(options);
|
|
57
|
-
}
|
|
58
|
-
return createManualRouter(options);
|
|
59
|
-
}
|
|
60
|
-
function createAutoRouter(options) {
|
|
61
27
|
const { renderFunction, enablePlatformDetection = true } = options;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
28
|
+
const security = options.security ?? (0, security_1.resolveSecurityOptions)(options.securityOptions);
|
|
29
|
+
const providesOwnRuntime = Boolean(options.moduleLoader || options.templateHtml);
|
|
30
|
+
const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
|
|
65
31
|
const mainRouter = (0, express_1.Router)();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
32
|
+
let runtime = null;
|
|
33
|
+
/**
|
|
34
|
+
* El manifest vigente.
|
|
35
|
+
*
|
|
36
|
+
* Se guarda acá y no se lee del runtime en cada rearmado: en desarrollo hay
|
|
37
|
+
* que volver a escanear cuando aparece o desaparece un archivo, y el runtime
|
|
38
|
+
* lo construyó una sola vez al arrancar.
|
|
39
|
+
*/
|
|
40
|
+
let routes = null;
|
|
41
|
+
let state = null;
|
|
42
|
+
let pageRouter = null;
|
|
43
|
+
let assetMiddleware = null;
|
|
44
|
+
let configMiddleware = null;
|
|
45
|
+
let stale = false;
|
|
46
|
+
let rescan = false;
|
|
47
|
+
const markRescan = () => {
|
|
48
|
+
stale = true;
|
|
49
|
+
rescan = true;
|
|
50
|
+
};
|
|
51
|
+
const chainContext = () => ({
|
|
52
|
+
moduleLoader: runtime?.moduleLoader,
|
|
53
|
+
renderOptions: {
|
|
54
|
+
enablePlatformDetection,
|
|
55
|
+
renderFunction,
|
|
56
|
+
security,
|
|
57
|
+
viteConfig: () => runtime,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const rebuild = async (rescan = false) => {
|
|
61
|
+
stale = false;
|
|
62
|
+
if (rescan || !routes)
|
|
63
|
+
routes = (0, manifest_1.createRouteManifest)(await (0, scan_1.scanRoutes)());
|
|
64
|
+
state = await (0, router_1.buildRouterState)(routes, chainContext());
|
|
65
|
+
};
|
|
66
|
+
const setup = async () => {
|
|
67
|
+
// La configuración primero: de ella salen el prefijo de ruta, el prefijo de
|
|
68
|
+
// artefactos y las reglas del pipeline, y las tres se necesitan antes de
|
|
69
|
+
// armar el runtime.
|
|
70
|
+
const config = options.config ??
|
|
71
|
+
(await (0, config_1.loadConfig)(options.root ?? process.cwd(), options.mode ?? (isProduction ? 'production' : 'development')));
|
|
72
|
+
configMiddleware = (0, config_1.createConfigMiddleware)(config);
|
|
73
|
+
const assetPrefix = (0, config_1.assetPrefixFor)({
|
|
74
|
+
basePath: config.basePath,
|
|
75
|
+
cdnPrefix: config.assets?.cdnPrefix,
|
|
76
|
+
});
|
|
77
|
+
if (options.runtime) {
|
|
78
|
+
runtime = options.runtime;
|
|
79
|
+
routes = runtime.routes;
|
|
80
|
+
assetMiddleware = isProduction
|
|
81
|
+
? await (0, prod_server_1.createProdAssetMiddleware)(undefined, { buildOutputs: runtime.buildOutputs })
|
|
82
|
+
: runtime.vite.middlewares;
|
|
73
83
|
}
|
|
74
|
-
else {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
else if (providesOwnRuntime) {
|
|
85
|
+
// Quien aporta su propio cargador de módulos no tiene runtime del
|
|
86
|
+
// framework, así que se arma uno mínimo con lo que declaró.
|
|
87
|
+
runtime = {
|
|
88
|
+
vite: null,
|
|
89
|
+
moduleLoader: options.moduleLoader,
|
|
90
|
+
templateHtml: options.templateHtml ?? '',
|
|
91
|
+
isProduction,
|
|
92
|
+
buildOutputs: new Set(),
|
|
93
|
+
close: async () => { },
|
|
94
|
+
};
|
|
78
95
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Productive middlewares
|
|
92
|
-
const prevMiddlewares = await (0, middlewares_1.loadPageMiddlewares)(page.route, viteConfig?.vite);
|
|
93
|
-
const serverSidePropsMiddleware = await (0, middlewares_1.getServerSidePropsAsMiddleware)(page.file, viteConfig?.vite);
|
|
94
|
-
const renderMiddleware = (0, render_1.createRenderMiddleware)(page, {
|
|
95
|
-
enablePlatformDetection,
|
|
96
|
-
renderFunction,
|
|
97
|
-
viteConfig: () => viteConfig // pasar por función para evitar capturar null
|
|
98
|
-
});
|
|
99
|
-
const pageMiddlewares = isProduction ? [
|
|
100
|
-
...prevMiddlewares,
|
|
101
|
-
serverSidePropsMiddleware,
|
|
102
|
-
renderMiddleware
|
|
103
|
-
] : [
|
|
104
|
-
HMRMiddleware,
|
|
105
|
-
renderMiddleware
|
|
106
|
-
];
|
|
107
|
-
mainRouter.get(page.expressRoute, ...pageMiddlewares);
|
|
108
|
-
// Error middleware separado
|
|
109
|
-
mainRouter.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, viteConfig?.vite));
|
|
96
|
+
else {
|
|
97
|
+
// El `base` de Vite se queda en `/`: el prefijo de ruta lo quita el
|
|
98
|
+
// pipeline de petición, y lo que se escribe en el HTML lo lleva por el
|
|
99
|
+
// prefijo de artefactos.
|
|
100
|
+
runtime = await (0, prod_server_1.createRuntime)(isProduction, options.basePath ?? process.env.BASE ?? '/', assetPrefix);
|
|
101
|
+
routes = runtime.routes;
|
|
102
|
+
// Los estáticos se atienden antes que las páginas: un pedido de artefacto
|
|
103
|
+
// no tiene que recorrer el manifest de rutas, y una ruta que capture todo
|
|
104
|
+
// no debe interceptarlos.
|
|
105
|
+
assetMiddleware = isProduction
|
|
106
|
+
? await (0, prod_server_1.createProdAssetMiddleware)(undefined, { buildOutputs: runtime.buildOutputs })
|
|
107
|
+
: runtime.vite.middlewares;
|
|
110
108
|
}
|
|
109
|
+
await rebuild();
|
|
110
|
+
pageRouter = (0, router_1.createPageRouter)(() => state, chainContext());
|
|
111
|
+
// Se marca y se rearma en la petición siguiente, no acá: el watcher avisa
|
|
112
|
+
// antes de que Vite termine de invalidar su grafo, y rearmar en ese instante
|
|
113
|
+
// volvería a leer la versión anterior del archivo.
|
|
114
|
+
runtime.onSourceChange?.(() => {
|
|
115
|
+
stale = true;
|
|
116
|
+
});
|
|
117
|
+
// Crear o borrar una página cambia el conjunto de rutas, no sólo el
|
|
118
|
+
// contenido de un módulo: hay que volver a escanear.
|
|
119
|
+
runtime.vite?.watcher?.on('add', markRescan);
|
|
120
|
+
runtime.vite?.watcher?.on('unlink', markRescan);
|
|
111
121
|
};
|
|
112
|
-
//
|
|
113
|
-
|
|
122
|
+
// El armado es asíncrono y una petición puede llegar antes de que termine. Se
|
|
123
|
+
// espera la promesa en vez de responder como si la ruta no existiera.
|
|
124
|
+
const ready = setup();
|
|
125
|
+
routerReadiness.set(mainRouter, ready);
|
|
126
|
+
mainRouter.use((req, res, next) => {
|
|
127
|
+
ready.then(() => {
|
|
128
|
+
const serve = () => {
|
|
129
|
+
// Se lee acá y no antes: la marca la pone el pipeline de configuración,
|
|
130
|
+
// que corre después de este callback.
|
|
131
|
+
const outside = req.ressOutsideBasePath === true;
|
|
132
|
+
// Después de la configuración, los artefactos: un pedido de artefacto no
|
|
133
|
+
// tiene que recorrer el manifest de rutas. Se registra acá y no con
|
|
134
|
+
// `use()` porque los dos middlewares se resuelven dentro del armado
|
|
135
|
+
// asíncrono, y registrarlos después dejaría al despachador por delante.
|
|
136
|
+
// Fuera del prefijo no hay páginas: declarar un prefijo y seguir
|
|
137
|
+
// sirviendo el sitio entero por la raíz es exactamente lo que un prefijo
|
|
138
|
+
// existe para evitar. Los artefactos sí se intentan igual, porque las
|
|
139
|
+
// rutas internas del servidor de desarrollo de Vite —`/@vite/client`,
|
|
140
|
+
// `/@fs/`— viven en la raíz y no bajo el prefijo de la aplicación.
|
|
141
|
+
const after = (err) => err ? next(err) : outside ? next() : dispatch(req, res, next);
|
|
142
|
+
if (!assetMiddleware)
|
|
143
|
+
return after();
|
|
144
|
+
assetMiddleware(req, res, after);
|
|
145
|
+
};
|
|
146
|
+
// Barra final, prefijo de ruta, redirecciones y reescrituras: todo lo que
|
|
147
|
+
// decide *qué* URL se está atendiendo va antes que quien la atiende.
|
|
148
|
+
if (!configMiddleware)
|
|
149
|
+
return serve();
|
|
150
|
+
configMiddleware(req, res, ((err) => (err ? next(err) : serve())));
|
|
151
|
+
}, next);
|
|
152
|
+
});
|
|
153
|
+
function dispatch(req, res, next) {
|
|
154
|
+
const run = () => (pageRouter ? pageRouter(req, res, next) : next());
|
|
155
|
+
if (!stale)
|
|
156
|
+
return run();
|
|
157
|
+
rebuild(rescan).then(() => { rescan = false; run(); }, next);
|
|
158
|
+
}
|
|
114
159
|
return mainRouter;
|
|
115
160
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const router = (0, express_1.Router)();
|
|
119
|
-
const pages = (0, pages_1.getPages)();
|
|
120
|
-
// Crear viteConfig para processRequest
|
|
121
|
-
const viteConfig = {
|
|
122
|
-
vite,
|
|
123
|
-
templateHtml: options.templateHtml || '',
|
|
124
|
-
isProduction: options.isProduction || false,
|
|
125
|
-
manifest: null
|
|
126
|
-
};
|
|
127
|
-
// Process pages sequentially to avoid race conditions
|
|
128
|
-
const setupRoutes = async () => {
|
|
129
|
-
for (const page of pages) {
|
|
130
|
-
// Dynamic middleware loading for HMR support(only in dev)
|
|
131
|
-
const HMRMiddleware = await (0, middlewares_1.createDynamicPageMiddlewares)(page.route, page.file, vite);
|
|
132
|
-
// Productive middlewares
|
|
133
|
-
const prevMiddlewares = await (0, middlewares_1.loadPageMiddlewares)(page.route, vite);
|
|
134
|
-
const serverSidePropsMiddleware = await (0, middlewares_1.getServerSidePropsAsMiddleware)(page.file, vite);
|
|
135
|
-
const renderMiddleware = (0, render_1.createRenderMiddleware)(page, {
|
|
136
|
-
enablePlatformDetection,
|
|
137
|
-
renderFunction,
|
|
138
|
-
viteConfig
|
|
139
|
-
});
|
|
140
|
-
const pageMiddlewares = options.isProduction ? [
|
|
141
|
-
...prevMiddlewares,
|
|
142
|
-
serverSidePropsMiddleware,
|
|
143
|
-
renderMiddleware
|
|
144
|
-
] : [
|
|
145
|
-
HMRMiddleware,
|
|
146
|
-
renderMiddleware
|
|
147
|
-
];
|
|
148
|
-
router.get(page.expressRoute, ...pageMiddlewares);
|
|
149
|
-
// Error middleware separado
|
|
150
|
-
router.use(page.expressRoute, (0, middlewares_1.createPageErrorHandler)(page.file, vite));
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
// Setup routes asynchronously but don't block router creation
|
|
154
|
-
setupRoutes().catch(console.error);
|
|
155
|
-
return router;
|
|
156
|
-
}
|
|
157
|
-
function listDetectedPages() {
|
|
158
|
-
return (0, pages_1.getPages)();
|
|
159
|
-
}
|
|
160
|
-
var pages_2 = require("./pages");
|
|
161
|
-
Object.defineProperty(exports, "getPages", { enumerable: true, get: function () { return pages_2.getPages; } });
|
|
161
|
+
var pages_1 = require("./pages");
|
|
162
|
+
Object.defineProperty(exports, "getPages", { enumerable: true, get: function () { return pages_1.getPages; } });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { RouteEntry, RouteManifest } from './types';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Request {
|
|
6
|
+
/** La ruta que atendió esta petición. La usa el manejador de errores. */
|
|
7
|
+
ressRoute?: RouteEntry;
|
|
8
|
+
/**
|
|
9
|
+
* Los parámetros de la URL.
|
|
10
|
+
*
|
|
11
|
+
* Express reasigna `req.params` al entrar a cada router anidado, así que
|
|
12
|
+
* los valores que el despachador extrajo se perderían en el camino. Acá
|
|
13
|
+
* quedan en un campo propio, y `req.params` se mantiene por compatibilidad
|
|
14
|
+
* con quien ya lo lee.
|
|
15
|
+
*/
|
|
16
|
+
ressParams?: Record<string, string | string[]>;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Cómo se arma la cadena de middlewares de una entrada. */
|
|
21
|
+
export type ChainBuilder = (entry: RouteEntry) => Promise<RequestHandler[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Un único punto de entrada para todas las rutas.
|
|
24
|
+
*
|
|
25
|
+
* El modelo anterior registraba una ruta de Express por página, lo que obligaba
|
|
26
|
+
* a traducir cada patrón a la sintaxis de Express —que no puede expresar un
|
|
27
|
+
* catch-all opcional ni un grupo— y dejaba la precedencia en manos del orden de
|
|
28
|
+
* registro. Acá el manifest decide, y Express sólo ve un middleware.
|
|
29
|
+
*
|
|
30
|
+
* Las cadenas se construyen una vez, no por petición. `chains` se reemplaza
|
|
31
|
+
* entero cuando el manifest cambia en desarrollo, de modo que una petición nunca
|
|
32
|
+
* ve una mezcla de dos versiones.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createDispatcher(getState: () => {
|
|
35
|
+
manifest: RouteManifest;
|
|
36
|
+
chains: Map<string, RequestHandler[]>;
|
|
37
|
+
}): RequestHandler;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDispatcher = createDispatcher;
|
|
4
|
+
/**
|
|
5
|
+
* Un único punto de entrada para todas las rutas.
|
|
6
|
+
*
|
|
7
|
+
* El modelo anterior registraba una ruta de Express por página, lo que obligaba
|
|
8
|
+
* a traducir cada patrón a la sintaxis de Express —que no puede expresar un
|
|
9
|
+
* catch-all opcional ni un grupo— y dejaba la precedencia en manos del orden de
|
|
10
|
+
* registro. Acá el manifest decide, y Express sólo ve un middleware.
|
|
11
|
+
*
|
|
12
|
+
* Las cadenas se construyen una vez, no por petición. `chains` se reemplaza
|
|
13
|
+
* entero cuando el manifest cambia en desarrollo, de modo que una petición nunca
|
|
14
|
+
* ve una mezcla de dos versiones.
|
|
15
|
+
*/
|
|
16
|
+
function createDispatcher(getState) {
|
|
17
|
+
return async (req, res, next) => {
|
|
18
|
+
const { manifest, chains } = getState();
|
|
19
|
+
const match = manifest.match(req.path ?? req.url ?? '/');
|
|
20
|
+
// Sin match no es un 404 todavía: puede haber middlewares detrás que sepan
|
|
21
|
+
// atenderla, y decidirlo acá les quitaría la oportunidad.
|
|
22
|
+
if (!match)
|
|
23
|
+
return next();
|
|
24
|
+
const chain = chains.get(match.entry.pageId);
|
|
25
|
+
if (!chain)
|
|
26
|
+
return next();
|
|
27
|
+
req.ressParams = match.params;
|
|
28
|
+
req.params = match.params;
|
|
29
|
+
req.ressRoute = match.entry;
|
|
30
|
+
runChain(chain, req, res, next);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Ejecuta la cadena respetando el contrato de Express.
|
|
35
|
+
*
|
|
36
|
+
* Cada middleware decide si sigue: `next()` avanza, `next(error)` corta y
|
|
37
|
+
* propaga. Un throw síncrono se convierte en `next(error)` para que no quede una
|
|
38
|
+
* petición colgada.
|
|
39
|
+
*/
|
|
40
|
+
function runChain(chain, req, res, done) {
|
|
41
|
+
let index = 0;
|
|
42
|
+
const step = (error) => {
|
|
43
|
+
if (error)
|
|
44
|
+
return done(error);
|
|
45
|
+
const handler = chain[index++];
|
|
46
|
+
if (!handler)
|
|
47
|
+
return done();
|
|
48
|
+
// Un middleware asíncrono puede llamar a `next()` y rechazar después. Sin
|
|
49
|
+
// esta guarda, el rechazo volvería a entrar a la cadena sobre una respuesta
|
|
50
|
+
// que ya se envió, y el fallo sería un ERR_HTTP_HEADERS_SENT en vez de la
|
|
51
|
+
// página de error.
|
|
52
|
+
let advanced = false;
|
|
53
|
+
const once = (err) => {
|
|
54
|
+
if (advanced)
|
|
55
|
+
return;
|
|
56
|
+
advanced = true;
|
|
57
|
+
step(err);
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
const result = handler(req, res, once);
|
|
61
|
+
if (result instanceof Promise)
|
|
62
|
+
result.catch(once);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
once(err);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
step();
|
|
69
|
+
}
|
|
@@ -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,56 @@
|
|
|
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
|
+
match(pathname, options = {}) {
|
|
27
|
+
const kinds = options.kinds ?? ['page'];
|
|
28
|
+
const clean = '/' + pathname.split('?')[0].split('#')[0].split('/').filter(Boolean).join('/');
|
|
29
|
+
for (const kind of kinds) {
|
|
30
|
+
const exact = staticIndex.get(`${kind} ${clean}`);
|
|
31
|
+
if (exact)
|
|
32
|
+
return { entry: exact, params: {} };
|
|
33
|
+
}
|
|
34
|
+
return (0, match_1.matchEntries)(entries, pathname, options);
|
|
35
|
+
},
|
|
36
|
+
get: (pageId) => byPageId.get(pageId),
|
|
37
|
+
byKind: (kind) => entries.filter((e) => e.kind === kind),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function serializeRouteManifest(data) {
|
|
41
|
+
return JSON.stringify(data, null, 2);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Lee un manifest emitido por el build.
|
|
45
|
+
*
|
|
46
|
+
* La versión se comprueba a propósito: un manifest de un formato anterior
|
|
47
|
+
* produciría fallos de ruteo difíciles de atribuir, y el mensaje dice qué hacer.
|
|
48
|
+
*/
|
|
49
|
+
function loadRouteManifest(raw) {
|
|
50
|
+
const data = JSON.parse(raw);
|
|
51
|
+
if (data.version !== types_1.ROUTE_MANIFEST_VERSION) {
|
|
52
|
+
throw new Error(`[ress] El manifest de rutas es de la versión ${data.version} y este ress.js espera la ` +
|
|
53
|
+
`${types_1.ROUTE_MANIFEST_VERSION}. Volvé a construir el proyecto.`);
|
|
54
|
+
}
|
|
55
|
+
return createRouteManifest(data);
|
|
56
|
+
}
|
|
@@ -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,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;
|