@ressjs/vite-router 0.5.2 → 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 +42 -32
- 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 -105
- 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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carga de módulos de usuario — páginas y middlewares.
|
|
3
|
+
*
|
|
4
|
+
* La interfaz es única para desarrollo y producción, de modo que quien carga un
|
|
5
|
+
* módulo no tenga que saber en qué entorno corre.
|
|
6
|
+
*/
|
|
7
|
+
/** Superficie mínima de un entorno de desarrollo de Vite que necesitamos. */
|
|
8
|
+
export interface FetchableEnvironment {
|
|
9
|
+
fetchModule(id: string, importer?: string, options?: {
|
|
10
|
+
cached?: boolean;
|
|
11
|
+
startOffset?: number;
|
|
12
|
+
}): Promise<Record<string, unknown>>;
|
|
13
|
+
}
|
|
14
|
+
export interface ModuleLoader {
|
|
15
|
+
/** Carga un módulo de usuario y devuelve sus exports. */
|
|
16
|
+
load<T = Record<string, unknown>>(id: string): Promise<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Descarta de la caché el módulo correspondiente a un archivo, para que la
|
|
19
|
+
* próxima carga lo vuelva a evaluar. Sin argumento, descarta todo.
|
|
20
|
+
*/
|
|
21
|
+
invalidate(file?: string): void;
|
|
22
|
+
/** Libera recursos. Idempotente. */
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Cargador de desarrollo.
|
|
27
|
+
*
|
|
28
|
+
* El runner se construye sobre `environment.fetchModule()` y no sobre el getter
|
|
29
|
+
* `RunnableDevEnvironment.runner`, porque ese getter arma su transporte de forma
|
|
30
|
+
* síncrona leyendo `environment.hot.api.outsideEmitter`, propiedad que sólo
|
|
31
|
+
* existe una vez que el servidor empezó a escuchar. Ress.js carga módulos de
|
|
32
|
+
* usuario durante el registro de rutas, antes de ese momento, y ahí el getter
|
|
33
|
+
* falla con `Cannot read properties of undefined (reading 'outsideEmitter')`.
|
|
34
|
+
* `fetchModule()` es un método asíncrono plano, disponible en todos los tipos de
|
|
35
|
+
* entorno y en cualquier punto del ciclo de vida.
|
|
36
|
+
*
|
|
37
|
+
* El runner se crea una vez por servidor, nunca por petición: `runner.import()`
|
|
38
|
+
* mantiene su propia caché de módulos y crear uno por petición la anularía.
|
|
39
|
+
*
|
|
40
|
+
* Esa caché es también la razón de `invalidate()`. El runner corre con `hmr:
|
|
41
|
+
* false` —el canal de HMR no es utilizable durante el registro de rutas—, así
|
|
42
|
+
* que nada descarta un módulo cuando su archivo cambia. Sin invalidación
|
|
43
|
+
* explícita, editar una página o un middleware no tendría efecto hasta reiniciar
|
|
44
|
+
* el servidor. `createDevRuntime` conecta el watcher de Vite a este método.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createDevModuleLoader(env: FetchableEnvironment): Promise<ModuleLoader>;
|
|
47
|
+
/**
|
|
48
|
+
* Cargador de producción: importa el artefacto ya compilado.
|
|
49
|
+
*
|
|
50
|
+
* `resolveId` traduce el identificador lógico de una página al path de su
|
|
51
|
+
* artefacto de servidor. Se recibe como parámetro para que la traducción sea
|
|
52
|
+
* responsabilidad de quien conoce la correspondencia — F-018 la toma del
|
|
53
|
+
* manifest de rutas, donde el build la registró.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createProdModuleLoader(resolveId: (id: string) => string): ModuleLoader;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Carga de módulos de usuario — páginas y middlewares.
|
|
4
|
+
*
|
|
5
|
+
* La interfaz es única para desarrollo y producción, de modo que quien carga un
|
|
6
|
+
* módulo no tenga que saber en qué entorno corre.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createDevModuleLoader = createDevModuleLoader;
|
|
13
|
+
exports.createProdModuleLoader = createProdModuleLoader;
|
|
14
|
+
/**
|
|
15
|
+
* Cargador de desarrollo.
|
|
16
|
+
*
|
|
17
|
+
* El runner se construye sobre `environment.fetchModule()` y no sobre el getter
|
|
18
|
+
* `RunnableDevEnvironment.runner`, porque ese getter arma su transporte de forma
|
|
19
|
+
* síncrona leyendo `environment.hot.api.outsideEmitter`, propiedad que sólo
|
|
20
|
+
* existe una vez que el servidor empezó a escuchar. Ress.js carga módulos de
|
|
21
|
+
* usuario durante el registro de rutas, antes de ese momento, y ahí el getter
|
|
22
|
+
* falla con `Cannot read properties of undefined (reading 'outsideEmitter')`.
|
|
23
|
+
* `fetchModule()` es un método asíncrono plano, disponible en todos los tipos de
|
|
24
|
+
* entorno y en cualquier punto del ciclo de vida.
|
|
25
|
+
*
|
|
26
|
+
* El runner se crea una vez por servidor, nunca por petición: `runner.import()`
|
|
27
|
+
* mantiene su propia caché de módulos y crear uno por petición la anularía.
|
|
28
|
+
*
|
|
29
|
+
* Esa caché es también la razón de `invalidate()`. El runner corre con `hmr:
|
|
30
|
+
* false` —el canal de HMR no es utilizable durante el registro de rutas—, así
|
|
31
|
+
* que nada descarta un módulo cuando su archivo cambia. Sin invalidación
|
|
32
|
+
* explícita, editar una página o un middleware no tendría efecto hasta reiniciar
|
|
33
|
+
* el servidor. `createDevRuntime` conecta el watcher de Vite a este método.
|
|
34
|
+
*/
|
|
35
|
+
async function createDevModuleLoader(env) {
|
|
36
|
+
const { ModuleRunner, ESModulesEvaluator, createNodeImportMeta } = await import(
|
|
37
|
+
/* @vite-ignore */ 'vite/module-runner');
|
|
38
|
+
const runner = new ModuleRunner({
|
|
39
|
+
transport: {
|
|
40
|
+
// `normalizeModuleRunnerTransport` ya desempaqueta el payload, así que
|
|
41
|
+
// `payload.data` llega como `{ id, name, data: args }`.
|
|
42
|
+
invoke: async (payload) => {
|
|
43
|
+
const { name, data: args } = payload.data;
|
|
44
|
+
if (name === 'fetchModule') {
|
|
45
|
+
const [id, importer, options] = args;
|
|
46
|
+
return { result: await env.fetchModule(id, importer, options) };
|
|
47
|
+
}
|
|
48
|
+
if (name === 'getBuiltins') {
|
|
49
|
+
// Los módulos corren en el proceso Node anfitrión, que ya tiene
|
|
50
|
+
// acceso nativo a los built-ins.
|
|
51
|
+
return { result: [] };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
error: {
|
|
55
|
+
name: 'Error',
|
|
56
|
+
message: `[ress] invocación inesperada del module runner: ${name}`,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
createImportMeta: createNodeImportMeta,
|
|
62
|
+
sourcemapInterceptor: false,
|
|
63
|
+
hmr: false,
|
|
64
|
+
}, new ESModulesEvaluator());
|
|
65
|
+
let closed = false;
|
|
66
|
+
return {
|
|
67
|
+
load: (id) => runner.import(id),
|
|
68
|
+
invalidate: (file) => {
|
|
69
|
+
if (!file) {
|
|
70
|
+
runner.clearCache();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Un archivo puede tener varios módulos evaluados si se importó con
|
|
74
|
+
// distintas queries. Se descartan todos.
|
|
75
|
+
const mods = runner.evaluatedModules.getModulesByFile(file);
|
|
76
|
+
if (!mods || mods.size === 0) {
|
|
77
|
+
// El archivo no está en el grafo de este runner: nada que descartar.
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const mod of mods) {
|
|
81
|
+
runner.evaluatedModules.invalidateModule(mod);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
close: async () => {
|
|
85
|
+
if (closed)
|
|
86
|
+
return;
|
|
87
|
+
closed = true;
|
|
88
|
+
await runner.close();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Cargador de producción: importa el artefacto ya compilado.
|
|
94
|
+
*
|
|
95
|
+
* `resolveId` traduce el identificador lógico de una página al path de su
|
|
96
|
+
* artefacto de servidor. Se recibe como parámetro para que la traducción sea
|
|
97
|
+
* responsabilidad de quien conoce la correspondencia — F-018 la toma del
|
|
98
|
+
* manifest de rutas, donde el build la registró.
|
|
99
|
+
*/
|
|
100
|
+
function createProdModuleLoader(resolveId) {
|
|
101
|
+
return {
|
|
102
|
+
load: async (id) => {
|
|
103
|
+
const target = resolveId(id);
|
|
104
|
+
// El loader ESM de Node interpreta una ruta `C:\\...` como una URL con
|
|
105
|
+
// esquema `c:` en Windows. Convertir sólo las rutas de archivos mantiene
|
|
106
|
+
// intactos los identificadores `node:`, `data:` y `file:` usados por
|
|
107
|
+
// integraciones y tests.
|
|
108
|
+
const specifier = process.platform === 'win32' && !isModuleSpecifier(target)
|
|
109
|
+
? (0, node_url_1.pathToFileURL)(node_path_1.default.resolve(target)).href
|
|
110
|
+
: target;
|
|
111
|
+
return (await import(/* @vite-ignore */ specifier));
|
|
112
|
+
},
|
|
113
|
+
// En producción los artefactos no cambian mientras el servidor corre.
|
|
114
|
+
invalidate: () => { },
|
|
115
|
+
close: async () => { },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function isModuleSpecifier(value) {
|
|
119
|
+
return /^(?:node:|data:|file:|https?:)/i.test(value);
|
|
120
|
+
}
|
|
121
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
122
|
+
const node_url_1 = require("node:url");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime de producción.
|
|
3
|
+
*
|
|
4
|
+
* No instancia Vite: en producción se sirven artefactos ya construidos.
|
|
5
|
+
*/
|
|
6
|
+
import { type ResolvedSecurityOptions } from '../security';
|
|
7
|
+
import { type DevRuntimeOptions, type RessRuntime } from './dev-server';
|
|
8
|
+
/**
|
|
9
|
+
* Dónde quedó el artefacto de servidor de una página.
|
|
10
|
+
*
|
|
11
|
+
* La traducción del nombre la hace `toCompiledPath`, que es la única
|
|
12
|
+
* implementación del framework: antes vivía repetida en tres lugares con reglas
|
|
13
|
+
* que ya habían divergido —una sólo recortaba `.ts`, otra sólo `\w+` dentro de
|
|
14
|
+
* los corchetes—.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveServerModulePath(pageFile: string, ssrOutDir?: string): string;
|
|
17
|
+
export declare function createProdRuntime(opts?: {
|
|
18
|
+
clientOutDir?: string;
|
|
19
|
+
ssrOutDir?: string;
|
|
20
|
+
/** Prefijo de las URL de artefactos: prefijo de ruta o CDN. */
|
|
21
|
+
assetPrefix?: string;
|
|
22
|
+
}): Promise<RessRuntime>;
|
|
23
|
+
/**
|
|
24
|
+
* Assets estáticos en producción.
|
|
25
|
+
*
|
|
26
|
+
* Cada archivo se cachea según lo que su nombre garantice: los que llevan el
|
|
27
|
+
* hash de su contenido pueden guardarse para siempre, el resto tiene que
|
|
28
|
+
* revalidarse. El default es revalidar, porque un archivo guardado como
|
|
29
|
+
* inmutable por error no se puede invalidar en el navegador de quien ya lo bajó.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createProdAssetMiddleware(clientOutDir?: string, options?: {
|
|
32
|
+
security?: ResolvedSecurityOptions;
|
|
33
|
+
/** Archivos que emitió el build, tomados del manifest. */
|
|
34
|
+
buildOutputs?: ReadonlySet<string>;
|
|
35
|
+
}): Promise<import("express-serve-static-core").Router>;
|
|
36
|
+
/** Los archivos que el build de cliente emitió, según el manifest. */
|
|
37
|
+
export declare function buildOutputsFrom(manifest: Record<string, {
|
|
38
|
+
file: string;
|
|
39
|
+
css?: string[];
|
|
40
|
+
}>): Set<string>;
|
|
41
|
+
/** Punto de entrada único: elige el runtime según el entorno. */
|
|
42
|
+
export declare function createRuntime(isProduction: boolean, base?: string, assetPrefix?: string, devOptions?: DevRuntimeOptions): Promise<RessRuntime>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime de producción.
|
|
4
|
+
*
|
|
5
|
+
* No instancia Vite: en producción se sirven artefactos ya construidos.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.resolveServerModulePath = resolveServerModulePath;
|
|
12
|
+
exports.createProdRuntime = createProdRuntime;
|
|
13
|
+
exports.createProdAssetMiddleware = createProdAssetMiddleware;
|
|
14
|
+
exports.buildOutputsFrom = buildOutputsFrom;
|
|
15
|
+
exports.createRuntime = createRuntime;
|
|
16
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const express_1 = __importDefault(require("express"));
|
|
19
|
+
const security_1 = require("../security");
|
|
20
|
+
const module_loader_1 = require("./module-loader");
|
|
21
|
+
const index_1 = require("../plugin/index");
|
|
22
|
+
const assets_1 = require("../variants/assets");
|
|
23
|
+
const manifest_1 = require("../routes/manifest");
|
|
24
|
+
const environments_1 = require("../plugin/environments");
|
|
25
|
+
const module_extensions_1 = require("../fs/module-extensions");
|
|
26
|
+
const dev_server_1 = require("./dev-server");
|
|
27
|
+
const template_1 = require("./template");
|
|
28
|
+
/**
|
|
29
|
+
* El manifest de rutas que emitió el build.
|
|
30
|
+
*
|
|
31
|
+
* Si no está —un proyecto construido con una versión anterior— se escanea, para
|
|
32
|
+
* no dejar el servidor sin arrancar por un archivo que se puede regenerar.
|
|
33
|
+
*/
|
|
34
|
+
async function loadRoutes(clientOutDir) {
|
|
35
|
+
const manifestPath = node_path_1.default.join(node_path_1.default.dirname(clientOutDir), 'route-manifest.json');
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = await promises_1.default.readFile(manifestPath, 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error(`[ress] No se encontró el manifest de rutas en ${manifestPath}. ` +
|
|
42
|
+
'Ejecutá el build antes de arrancar en producción.');
|
|
43
|
+
}
|
|
44
|
+
// Un manifest ilegible o de otra versión se reporta tal cual. Escanear el
|
|
45
|
+
// proyecto como respaldo era peor que fallar: en un servidor que sólo tiene
|
|
46
|
+
// `dist/` el escaneo no encuentra nada, y en vez de un error claro el servidor
|
|
47
|
+
// arranca y responde 404 en todas las rutas.
|
|
48
|
+
return (0, manifest_1.loadRouteManifest)(raw);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Dónde quedó el artefacto de servidor de una página.
|
|
52
|
+
*
|
|
53
|
+
* La traducción del nombre la hace `toCompiledPath`, que es la única
|
|
54
|
+
* implementación del framework: antes vivía repetida en tres lugares con reglas
|
|
55
|
+
* que ya habían divergido —una sólo recortaba `.ts`, otra sólo `\w+` dentro de
|
|
56
|
+
* los corchetes—.
|
|
57
|
+
*/
|
|
58
|
+
function resolveServerModulePath(pageFile, ssrOutDir = environments_1.OUT_DIR.ssr) {
|
|
59
|
+
return node_path_1.default.resolve(process.cwd(), ssrOutDir, (0, module_extensions_1.toCompiledPath)(pageFile));
|
|
60
|
+
}
|
|
61
|
+
async function createProdRuntime(opts = {}) {
|
|
62
|
+
const clientOutDir = opts.clientOutDir ?? environments_1.OUT_DIR.client;
|
|
63
|
+
const ssrOutDir = opts.ssrOutDir ?? environments_1.OUT_DIR.ssr;
|
|
64
|
+
const manifestPath = node_path_1.default.join(clientOutDir, '.vite', 'manifest.json');
|
|
65
|
+
let manifest = {};
|
|
66
|
+
try {
|
|
67
|
+
manifest = JSON.parse(await promises_1.default.readFile(manifestPath, 'utf-8'));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new Error(`[ress] No se encontró el manifest del cliente en ${manifestPath}. ` +
|
|
71
|
+
'Ejecutá el build antes de arrancar en producción.');
|
|
72
|
+
}
|
|
73
|
+
const templateHtml = await (0, template_1.readTemplateHtml)();
|
|
74
|
+
const moduleLoader = (0, module_loader_1.createProdModuleLoader)((id) => resolveServerModulePath(id, ssrOutDir));
|
|
75
|
+
// El mismo catálogo que en desarrollo: qué variantes existen lo dicen los
|
|
76
|
+
// archivos del proyecto, y el manifest sólo aporta a qué artefacto construido
|
|
77
|
+
// corresponde cada una.
|
|
78
|
+
const catalog = await (0, index_1.refreshVariantCatalog)();
|
|
79
|
+
// El manifest lo emite el build. Si no está —un proyecto construido con una
|
|
80
|
+
// versión anterior— se escanea, para no dejar el servidor sin arrancar por
|
|
81
|
+
// un archivo que se puede regenerar.
|
|
82
|
+
const routes = await loadRoutes(clientOutDir);
|
|
83
|
+
return {
|
|
84
|
+
vite: null,
|
|
85
|
+
moduleLoader,
|
|
86
|
+
templateHtml,
|
|
87
|
+
isProduction: true,
|
|
88
|
+
catalog,
|
|
89
|
+
routes,
|
|
90
|
+
assets: (0, assets_1.createProdAssetIndex)(catalog, manifest, opts.assetPrefix ?? '/'),
|
|
91
|
+
buildOutputs: buildOutputsFrom(manifest),
|
|
92
|
+
// Los artefactos no cambian mientras el servidor corre.
|
|
93
|
+
onSourceChange: () => () => { },
|
|
94
|
+
close: async () => {
|
|
95
|
+
await moduleLoader.close();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Assets estáticos en producción.
|
|
101
|
+
*
|
|
102
|
+
* Cada archivo se cachea según lo que su nombre garantice: los que llevan el
|
|
103
|
+
* hash de su contenido pueden guardarse para siempre, el resto tiene que
|
|
104
|
+
* revalidarse. El default es revalidar, porque un archivo guardado como
|
|
105
|
+
* inmutable por error no se puede invalidar en el navegador de quien ya lo bajó.
|
|
106
|
+
*/
|
|
107
|
+
async function createProdAssetMiddleware(clientOutDir = environments_1.OUT_DIR.client, options = {}) {
|
|
108
|
+
const compression = (await import('compression')).default;
|
|
109
|
+
const sirv = (await import('sirv')).default;
|
|
110
|
+
const security = options.security ?? (0, security_1.resolveSecurityOptions)();
|
|
111
|
+
const buildOutputs = options.buildOutputs ?? new Set();
|
|
112
|
+
const middlewares = express_1.default.Router();
|
|
113
|
+
middlewares.use(compression());
|
|
114
|
+
middlewares.use(sirv(clientOutDir, {
|
|
115
|
+
extensions: [],
|
|
116
|
+
etag: true,
|
|
117
|
+
setHeaders(res, pathname) {
|
|
118
|
+
if (security.headers.contentTypeOptions) {
|
|
119
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
120
|
+
}
|
|
121
|
+
res.setHeader('Cache-Control', (0, security_1.assetCacheControl)((0, security_1.classifyAsset)(pathname, buildOutputs), security));
|
|
122
|
+
},
|
|
123
|
+
}));
|
|
124
|
+
return middlewares;
|
|
125
|
+
}
|
|
126
|
+
/** Los archivos que el build de cliente emitió, según el manifest. */
|
|
127
|
+
function buildOutputsFrom(manifest) {
|
|
128
|
+
const out = new Set();
|
|
129
|
+
for (const chunk of Object.values(manifest)) {
|
|
130
|
+
if (chunk.file)
|
|
131
|
+
out.add(chunk.file);
|
|
132
|
+
for (const css of chunk.css ?? [])
|
|
133
|
+
out.add(css);
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
/** Punto de entrada único: elige el runtime según el entorno. */
|
|
138
|
+
async function createRuntime(isProduction, base = '/', assetPrefix = base, devOptions = {}) {
|
|
139
|
+
if (isProduction)
|
|
140
|
+
return createProdRuntime({ assetPrefix });
|
|
141
|
+
return (0, dev_server_1.createDevRuntime)(base, assetPrefix, devOptions);
|
|
142
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documento mínimo para proyectos que no necesitan personalizar el shell HTML.
|
|
3
|
+
* Los marcadores son consumidos por generateCompleteHTML.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_TEMPLATE_HTML = "<!doctype html>\n<html lang=\"en\">\n <head><!--app-head--></head>\n <body><div id=\"root\"><!--app-html--></div></body>\n</html>\n";
|
|
6
|
+
/**
|
|
7
|
+
* Lee el shell del proyecto cuando existe y cae a la plantilla del paquete si no.
|
|
8
|
+
* Sólo ENOENT es una ausencia esperable; otros errores deben ser visibles.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readTemplateHtml(root?: string): Promise<string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
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.DEFAULT_TEMPLATE_HTML = void 0;
|
|
7
|
+
exports.readTemplateHtml = readTemplateHtml;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* Documento mínimo para proyectos que no necesitan personalizar el shell HTML.
|
|
12
|
+
* Los marcadores son consumidos por generateCompleteHTML.
|
|
13
|
+
*/
|
|
14
|
+
exports.DEFAULT_TEMPLATE_HTML = `<!doctype html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head><!--app-head--></head>
|
|
17
|
+
<body><div id="root"><!--app-html--></div></body>
|
|
18
|
+
</html>
|
|
19
|
+
`;
|
|
20
|
+
/**
|
|
21
|
+
* Lee el shell del proyecto cuando existe y cae a la plantilla del paquete si no.
|
|
22
|
+
* Sólo ENOENT es una ausencia esperable; otros errores deben ser visibles.
|
|
23
|
+
*/
|
|
24
|
+
async function readTemplateHtml(root = process.cwd()) {
|
|
25
|
+
try {
|
|
26
|
+
return await promises_1.default.readFile(node_path_1.default.join(root, 'index.html'), 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code !== 'ENOENT')
|
|
30
|
+
throw error;
|
|
31
|
+
return exports.DEFAULT_TEMPLATE_HTML;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué parte del estado del servidor viaja al cliente.
|
|
3
|
+
*
|
|
4
|
+
* La regla es enumerar lo que **sí** viaja. La inversa —enumerar lo que se
|
|
5
|
+
* excluye— falla sola: cada middleware que agregue una clave nueva a
|
|
6
|
+
* `res.locals` la expone hasta que alguien se acuerde de agregarla a la lista de
|
|
7
|
+
* exclusión, y nadie se acuerda.
|
|
8
|
+
*/
|
|
9
|
+
import type { ClientPropsPolicy } from './config';
|
|
10
|
+
/**
|
|
11
|
+
* Las props que se serializan en el documento.
|
|
12
|
+
*
|
|
13
|
+
* Las props del **servidor** no cambian: el componente sigue recibiendo todo
|
|
14
|
+
* durante el renderizado. La restricción es sólo sobre lo que se escribe en el
|
|
15
|
+
* HTML.
|
|
16
|
+
*/
|
|
17
|
+
export declare function pickClientProps(locals: Record<string, unknown>, policy: ClientPropsPolicy): Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* Qué quedó afuera.
|
|
20
|
+
*
|
|
21
|
+
* Se usa para avisar en desarrollo: una página que renderiza en el servidor con
|
|
22
|
+
* datos que el cliente no tiene hidrata distinto, y ese desajuste es difícil de
|
|
23
|
+
* diagnosticar sin que alguien lo nombre.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withheldKeys(locals: Record<string, unknown>, exposed: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Qué parte del estado del servidor viaja al cliente.
|
|
4
|
+
*
|
|
5
|
+
* La regla es enumerar lo que **sí** viaja. La inversa —enumerar lo que se
|
|
6
|
+
* excluye— falla sola: cada middleware que agregue una clave nueva a
|
|
7
|
+
* `res.locals` la expone hasta que alguien se acuerde de agregarla a la lista de
|
|
8
|
+
* exclusión, y nadie se acuerda.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.pickClientProps = pickClientProps;
|
|
12
|
+
exports.withheldKeys = withheldKeys;
|
|
13
|
+
/**
|
|
14
|
+
* Las props que se serializan en el documento.
|
|
15
|
+
*
|
|
16
|
+
* Las props del **servidor** no cambian: el componente sigue recibiendo todo
|
|
17
|
+
* durante el renderizado. La restricción es sólo sobre lo que se escribe en el
|
|
18
|
+
* HTML.
|
|
19
|
+
*/
|
|
20
|
+
function pickClientProps(locals, policy) {
|
|
21
|
+
// Sin prototipo: así una clave `__proto__` en los datos es una clave más y no
|
|
22
|
+
// altera el objeto.
|
|
23
|
+
const out = Object.create(null);
|
|
24
|
+
// `serverSideProps` es lo que la página pidió explícitamente para el cliente:
|
|
25
|
+
// es el contrato que ya usan las aplicaciones, y por eso sigue activo.
|
|
26
|
+
if (policy.exposeServerSideProps && isPlainObject(locals?.serverSideProps)) {
|
|
27
|
+
Object.assign(out, locals.serverSideProps);
|
|
28
|
+
}
|
|
29
|
+
// La válvula para el estado público que aporta un middleware, como el idioma.
|
|
30
|
+
for (const key of policy.expose) {
|
|
31
|
+
if (Object.hasOwn(locals ?? {}, key))
|
|
32
|
+
out[key] = locals[key];
|
|
33
|
+
}
|
|
34
|
+
// Configuración del servidor: nunca fue para el cliente, bajo ninguna política.
|
|
35
|
+
delete out.htmlConfig;
|
|
36
|
+
return { ...out };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Qué quedó afuera.
|
|
40
|
+
*
|
|
41
|
+
* Se usa para avisar en desarrollo: una página que renderiza en el servidor con
|
|
42
|
+
* datos que el cliente no tiene hidrata distinto, y ese desajuste es difícil de
|
|
43
|
+
* diagnosticar sin que alguien lo nombre.
|
|
44
|
+
*/
|
|
45
|
+
function withheldKeys(locals, exposed) {
|
|
46
|
+
return Object.keys(locals ?? {}).filter((key) => key !== 'htmlConfig' && key !== 'serverSideProps' && !(key in exposed));
|
|
47
|
+
}
|
|
48
|
+
function isPlainObject(value) {
|
|
49
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
50
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opciones de seguridad del framework.
|
|
3
|
+
*
|
|
4
|
+
* Se resuelven **una vez al arrancar**, no por petición: el objeto resultante
|
|
5
|
+
* está completo y congelado, y viaja por el contexto de seguridad hasta quien lo
|
|
6
|
+
* necesite. Resolver defaults por petición sería trabajo repetido y, peor,
|
|
7
|
+
* abriría la puerta a que dos partes de la misma respuesta usen valores
|
|
8
|
+
* distintos.
|
|
9
|
+
*/
|
|
10
|
+
/** Qué claves del estado del servidor viajan al cliente. */
|
|
11
|
+
export interface ClientPropsPolicy {
|
|
12
|
+
/** Claves de `res.locals` que se serializan. Nada más viaja. */
|
|
13
|
+
expose: string[];
|
|
14
|
+
/** Si `res.locals.serverSideProps` se expone completo. */
|
|
15
|
+
exposeServerSideProps: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface HstsOptions {
|
|
18
|
+
maxAge: number;
|
|
19
|
+
includeSubDomains?: boolean;
|
|
20
|
+
preload?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface SecurityOptions {
|
|
23
|
+
clientProps?: Partial<ClientPropsPolicy>;
|
|
24
|
+
maxStateBytes?: number;
|
|
25
|
+
onStateOverLimit?: 'warn' | 'error';
|
|
26
|
+
/**
|
|
27
|
+
* Si el User-Agent participa de la resolución **y** se declara en `Vary`.
|
|
28
|
+
*
|
|
29
|
+
* Apagado por defecto. Es la señal de mayor cardinalidad que existe —cientos
|
|
30
|
+
* de miles de valores distintos—, así que declararla equivale a decirle a toda
|
|
31
|
+
* caché intermedia que no guarde nada. Y usarla sin declararla sirve la
|
|
32
|
+
* variante equivocada, así que las dos decisiones son una sola.
|
|
33
|
+
*
|
|
34
|
+
* Con esto apagado, la variante se resuelve por lo que sí es barato de
|
|
35
|
+
* declarar: lo que el cliente manda en cabeceras, lo que el navegador aporta
|
|
36
|
+
* como pistas, y lo que la red de distribución ya calculó.
|
|
37
|
+
*/
|
|
38
|
+
varyOnUserAgent?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Si la plataforma detectada se escribe en el documento.
|
|
41
|
+
*
|
|
42
|
+
* `'auto'` la escribe sólo cuando la página tiene variantes. Una página que no
|
|
43
|
+
* varía devuelve el mismo documento a cualquier cliente, y escribir en él quién
|
|
44
|
+
* lo pidió lo vuelve distinto para cada uno sin que nada lo declare: una caché
|
|
45
|
+
* intermedia le serviría a un teléfono la plataforma de un televisor. Cuando no
|
|
46
|
+
* viaja, el cliente la detecta por su cuenta con las mismas reglas.
|
|
47
|
+
*/
|
|
48
|
+
serializePlatform?: 'auto' | 'always' | 'never';
|
|
49
|
+
headers?: {
|
|
50
|
+
contentTypeOptions?: boolean;
|
|
51
|
+
referrerPolicy?: string | false;
|
|
52
|
+
frameOptions?: 'DENY' | 'SAMEORIGIN' | false;
|
|
53
|
+
hsts?: HstsOptions | false;
|
|
54
|
+
};
|
|
55
|
+
cache?: {
|
|
56
|
+
html?: string;
|
|
57
|
+
immutableMaxAge?: number;
|
|
58
|
+
staticMaxAge?: number;
|
|
59
|
+
};
|
|
60
|
+
csp?: {
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
reportOnly?: boolean;
|
|
63
|
+
directives?: Record<string, string[] | false>;
|
|
64
|
+
reportUri?: string;
|
|
65
|
+
};
|
|
66
|
+
head?: {
|
|
67
|
+
allowScriptTags?: boolean;
|
|
68
|
+
extraTagAllowlist?: string[];
|
|
69
|
+
};
|
|
70
|
+
dev?: {
|
|
71
|
+
allowedHosts?: string[];
|
|
72
|
+
allowedOrigins?: string[] | false;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export interface ResolvedSecurityOptions {
|
|
76
|
+
clientProps: ClientPropsPolicy;
|
|
77
|
+
maxStateBytes: number;
|
|
78
|
+
onStateOverLimit: 'warn' | 'error';
|
|
79
|
+
varyOnUserAgent: boolean;
|
|
80
|
+
serializePlatform: 'auto' | 'always' | 'never';
|
|
81
|
+
headers: {
|
|
82
|
+
contentTypeOptions: boolean;
|
|
83
|
+
referrerPolicy: string | false;
|
|
84
|
+
frameOptions: 'DENY' | 'SAMEORIGIN' | false;
|
|
85
|
+
hsts: HstsOptions | false;
|
|
86
|
+
};
|
|
87
|
+
cache: {
|
|
88
|
+
html: string;
|
|
89
|
+
immutableMaxAge: number;
|
|
90
|
+
staticMaxAge: number;
|
|
91
|
+
};
|
|
92
|
+
csp: {
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
reportOnly: boolean;
|
|
95
|
+
directives: Record<string, string[] | false>;
|
|
96
|
+
reportUri?: string;
|
|
97
|
+
};
|
|
98
|
+
head: {
|
|
99
|
+
allowScriptTags: boolean;
|
|
100
|
+
extraTagAllowlist: string[];
|
|
101
|
+
};
|
|
102
|
+
dev: {
|
|
103
|
+
allowedHosts?: string[];
|
|
104
|
+
allowedOrigins: string[] | false;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Defaults.
|
|
109
|
+
*
|
|
110
|
+
* Dos ausencias son deliberadas y están explicadas donde se aplican:
|
|
111
|
+
* `frameOptions` apagada, porque ress.js sirve páginas embebidas por diseño, y
|
|
112
|
+
* `hsts` apagada, porque activarla sobre un dominio que todavía sirve HTTP por
|
|
113
|
+
* alguna ruta lo deja inaccesible.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveSecurityOptions(user?: SecurityOptions): ResolvedSecurityOptions;
|
|
116
|
+
/** Lo que necesita saber cada parte de la respuesta sobre esta petición. */
|
|
117
|
+
export interface SecurityContext {
|
|
118
|
+
/** Autoriza los bloques inline ante la política de contenido. */
|
|
119
|
+
nonce?: string;
|
|
120
|
+
isProduction: boolean;
|
|
121
|
+
options: ResolvedSecurityOptions;
|
|
122
|
+
/** Ruta de la petición, para que los avisos digan dónde. */
|
|
123
|
+
route: string;
|
|
124
|
+
/**
|
|
125
|
+
* Ejes por los que varía esta página. Vacío significa que su documento es el
|
|
126
|
+
* mismo para cualquier cliente, y entonces nada que dependa del cliente puede
|
|
127
|
+
* escribirse en él.
|
|
128
|
+
*/
|
|
129
|
+
varyBy?: readonly string[];
|
|
130
|
+
}
|