@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,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MANUAL_RESTART_KEYS = exports.RESTART_REASONS = exports.HOT_KEYS = void 0;
|
|
4
|
+
exports.changedKeys = changedKeys;
|
|
5
|
+
exports.manualRestartReason = manualRestartReason;
|
|
6
|
+
exports.restartReason = restartReason;
|
|
7
|
+
exports.isConfigLayer = isConfigLayer;
|
|
8
|
+
exports.watchConfig = watchConfig;
|
|
9
|
+
const config_1 = require("@ressjs/config");
|
|
10
|
+
const load_1 = require("./load");
|
|
11
|
+
/**
|
|
12
|
+
* Recarga de la configuración en desarrollo.
|
|
13
|
+
*
|
|
14
|
+
* Se observa la carpeta `config/`: cualquiera de sus capas, no sólo la del modo
|
|
15
|
+
* activo, porque `default` y `local` también participan de la fusión.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Claves que se pueden aplicar sin reiniciar.
|
|
19
|
+
*
|
|
20
|
+
* Todas se consultan al atender una petición, así que la siguiente ya ve el
|
|
21
|
+
* valor nuevo.
|
|
22
|
+
*/
|
|
23
|
+
// `security` no está acá: `createViteRouter`/`ressCorePlugin` la resuelven una
|
|
24
|
+
// sola vez, dentro de `setup()`/el hook `config`, así que un cambio en caliente
|
|
25
|
+
// de `config/*.js` no la vuelve a completar. Está en `RESTART_REASONS`.
|
|
26
|
+
exports.HOT_KEYS = ['redirects', 'rewrites', 'headers', 'trailingSlash'];
|
|
27
|
+
/**
|
|
28
|
+
* Claves que exigen reiniciar, y por qué.
|
|
29
|
+
*
|
|
30
|
+
* El framework lo dice nombrando la clave en vez de aplicar la mitad de la
|
|
31
|
+
* configuración: media configuración aplicada es un estado que nadie declaró y
|
|
32
|
+
* que produce errores que no se corresponden con ningún archivo.
|
|
33
|
+
*/
|
|
34
|
+
exports.RESTART_REASONS = {
|
|
35
|
+
basePath: 'cambia la URL bajo la que se sirve todo, incluidos los artefactos ya emitidos',
|
|
36
|
+
extensions: 'cambia qué archivos son páginas, y eso redefine el catálogo de entradas',
|
|
37
|
+
platform: 'redefine los ejes de plataforma, y con ellos las variantes que se construyen',
|
|
38
|
+
publicEnv: 'las variables se reemplazan en tiempo de build, no al atender una petición',
|
|
39
|
+
assets: 'el índice de artefactos se arma una vez, con el prefijo vigente al arrancar',
|
|
40
|
+
security: 'las cabeceras, la política de contenido y los límites se resuelven una vez al armar el router',
|
|
41
|
+
isr: 'la caché incremental se crea una vez, con sus límites y su secreto de vista previa',
|
|
42
|
+
// F-033: `beforeRouter`/`afterRouter` se montan una vez, al construir la
|
|
43
|
+
// aplicación de servidor. Tratar su cambio como aplicable en caliente
|
|
44
|
+
// dejaría un "recargado" en el log sin ningún efecto real.
|
|
45
|
+
server: 'los middlewares de la vía de escape se montan una sola vez, al construir el servidor',
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Claves que se leen al construir la aplicación de servidor de ress —el
|
|
49
|
+
* router, la caché incremental, los middlewares de la vía de escape—, no el
|
|
50
|
+
* servidor de Vite.
|
|
51
|
+
*
|
|
52
|
+
* Reiniciar Vite no las aplica: la CLI arma esa aplicación una sola vez, fuera
|
|
53
|
+
* de Vite. Para ellas no se reinicia nada ni se dice que se reinició: se pide
|
|
54
|
+
* detener y volver a arrancar `ress dev`.
|
|
55
|
+
*/
|
|
56
|
+
exports.MANUAL_RESTART_KEYS = new Set([
|
|
57
|
+
'basePath',
|
|
58
|
+
'assets',
|
|
59
|
+
'security',
|
|
60
|
+
'isr',
|
|
61
|
+
'server',
|
|
62
|
+
]);
|
|
63
|
+
/** Qué claves de primer nivel cambiaron entre dos configuraciones resueltas. */
|
|
64
|
+
function changedKeys(previous, next) {
|
|
65
|
+
const keys = new Set([...Object.keys(previous), ...Object.keys(next)]);
|
|
66
|
+
const changed = [];
|
|
67
|
+
for (const key of keys) {
|
|
68
|
+
const a = stable(previous[key]);
|
|
69
|
+
const b = stable(next[key]);
|
|
70
|
+
if (a !== b)
|
|
71
|
+
changed.push(key);
|
|
72
|
+
}
|
|
73
|
+
return changed.sort();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Serialización estable para comparar.
|
|
77
|
+
*
|
|
78
|
+
* Las claves de un objeto se ordenan: dos configuraciones equivalentes escritas
|
|
79
|
+
* en distinto orden no son un cambio, y tratarlas como tal reiniciaría el
|
|
80
|
+
* servidor por nada.
|
|
81
|
+
*/
|
|
82
|
+
function stable(value) {
|
|
83
|
+
if (typeof value === 'function')
|
|
84
|
+
return `fn:${String(value)}`;
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return 'undefined';
|
|
87
|
+
return JSON.stringify(value, (_key, inner) => {
|
|
88
|
+
if (typeof inner === 'function')
|
|
89
|
+
return `fn:${String(inner)}`;
|
|
90
|
+
if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
|
|
91
|
+
return Object.fromEntries(Object.entries(inner).sort(([a], [b]) => a.localeCompare(b)));
|
|
92
|
+
}
|
|
93
|
+
return inner;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Si alguna de las claves cambiadas exige volver a arrancar `ress dev` a mano. */
|
|
97
|
+
function manualRestartReason(changed) {
|
|
98
|
+
for (const key of changed) {
|
|
99
|
+
if (exports.MANUAL_RESTART_KEYS.has(key))
|
|
100
|
+
return `\`${key}\`: ${exports.RESTART_REASONS[key]}`;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/** El motivo del reinicio, o nada si todo lo que cambió se aplica en caliente. */
|
|
105
|
+
function restartReason(changed) {
|
|
106
|
+
for (const key of changed) {
|
|
107
|
+
const reason = exports.RESTART_REASONS[key];
|
|
108
|
+
if (reason)
|
|
109
|
+
return `\`${key}\`: ${reason}`;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/** ¿Este archivo es una capa de configuración del proyecto? */
|
|
114
|
+
function isConfigLayer(file, root) {
|
|
115
|
+
const normalized = file.replace(/\\/g, '/');
|
|
116
|
+
const base = root.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
117
|
+
if (!normalized.startsWith(base + '/'))
|
|
118
|
+
return false;
|
|
119
|
+
const relative = normalized.slice(base.length + 1);
|
|
120
|
+
return /^config\/[^/]+$/.test(relative) || /^config\.[^/]+$/.test(relative);
|
|
121
|
+
}
|
|
122
|
+
/** Conecta el observador de Vite con la carpeta de configuración. */
|
|
123
|
+
function watchConfig(server, options) {
|
|
124
|
+
const log = options.log ?? ((message) => console.log(message));
|
|
125
|
+
const restart = options.onRestart ??
|
|
126
|
+
((reason) => {
|
|
127
|
+
log(`[ress] Reiniciando: ${reason}`);
|
|
128
|
+
void server?.restart?.();
|
|
129
|
+
});
|
|
130
|
+
const onChange = async (file) => {
|
|
131
|
+
if (!isConfigLayer(file, options.root))
|
|
132
|
+
return;
|
|
133
|
+
// Lo resuelto se descarta entero: la fusión depende de todas las capas, así
|
|
134
|
+
// que un cambio en cualquiera de ellas invalida el resultado.
|
|
135
|
+
(0, config_1.invalidateConfig)();
|
|
136
|
+
let next;
|
|
137
|
+
try {
|
|
138
|
+
next = await (0, load_1.loadConfig)(options.root, options.mode);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
// Un archivo a medio escribir no tiene que voltear el servidor: se informa
|
|
142
|
+
// y se sigue con la configuración anterior hasta que vuelva a guardarse.
|
|
143
|
+
log(`[ress] La configuración no se pudo cargar: ${error.message}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const changed = changedKeys(options.current(), next);
|
|
147
|
+
if (changed.length === 0)
|
|
148
|
+
return;
|
|
149
|
+
// Lo que no aplica ni Vite: se dice qué hacer en vez de fingir un reinicio.
|
|
150
|
+
const manual = manualRestartReason(changed);
|
|
151
|
+
if (manual) {
|
|
152
|
+
return log(`[ress] Cambió ${manual}. Detené y volvé a arrancar \`ress dev\` para aplicarlo.`);
|
|
153
|
+
}
|
|
154
|
+
const reason = restartReason(changed);
|
|
155
|
+
if (reason)
|
|
156
|
+
return restart(reason);
|
|
157
|
+
options.onReload(next);
|
|
158
|
+
log(`[ress] Configuración recargada: ${changed.join(', ')}`);
|
|
159
|
+
};
|
|
160
|
+
for (const event of ['add', 'change', 'unlink']) {
|
|
161
|
+
server?.watcher?.on(event, (file) => void onChange(file));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué extensiones acepta el framework, declarado una sola vez.
|
|
3
|
+
*
|
|
4
|
+
* Antes cada punto del pipeline tenía su propia idea: el descubrimiento globeaba
|
|
5
|
+
* `.tsx` y `.jsx`, el render exigía `.tsx` y lanzaba con cualquier otra cosa, y
|
|
6
|
+
* la carga en producción ramificaba por extensión. El resultado era que un
|
|
7
|
+
* proyecto en JavaScript descubría sus páginas y reventaba al renderizarlas.
|
|
8
|
+
*
|
|
9
|
+
* Ninguna función de este módulo toca el sistema de archivos: son puras sobre
|
|
10
|
+
* cadenas, y por eso se pueden probar sin montar un proyecto.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SCRIPT_EXTENSIONS: readonly [".tsx", ".ts", ".jsx", ".js"];
|
|
13
|
+
export type ScriptExtension = (typeof SCRIPT_EXTENSIONS)[number];
|
|
14
|
+
export interface ExtensionPolicy {
|
|
15
|
+
/** Extensiones válidas para un archivo de página. */
|
|
16
|
+
page: readonly ScriptExtension[];
|
|
17
|
+
/** Extensiones válidas para un archivo de middleware. */
|
|
18
|
+
middleware: readonly ScriptExtension[];
|
|
19
|
+
/** Extensión que produce el build. Siempre una sola. */
|
|
20
|
+
compiled: '.js';
|
|
21
|
+
/** Sufijos que se ignoran aunque la extensión coincida. */
|
|
22
|
+
ignoredSuffixes: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_EXTENSION_POLICY: ExtensionPolicy;
|
|
25
|
+
export interface PageExclusion {
|
|
26
|
+
/** Identifica la convención que reservó el nombre, para diagnósticos. */
|
|
27
|
+
owner: string;
|
|
28
|
+
/** Se evalúa sobre la ruta relativa a pages, sin extensión. */
|
|
29
|
+
matches(relativePath: string): boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convenciones de archivos que pueden vivir bajo `app/pages` sin ser páginas.
|
|
33
|
+
*
|
|
34
|
+
* Este es el único registro: tanto el descubrimiento legado como el manifest
|
|
35
|
+
* consultan `isPageFile`, de modo que ampliar las extensiones no convierte un
|
|
36
|
+
* middleware, test o auxiliar en una ruta accidental.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_EXCLUSIONS: readonly PageExclusion[];
|
|
39
|
+
/** `['.tsx','.ts']` → `{tsx,ts}`, la forma que entiende un glob. */
|
|
40
|
+
export declare function globBrace(extensions: readonly ScriptExtension[]): string;
|
|
41
|
+
export declare function pageGlob(policy: ExtensionPolicy, pagesDir?: string): string;
|
|
42
|
+
export declare function middlewareGlobs(policy: ExtensionPolicy, pagesDir?: string): string[];
|
|
43
|
+
/**
|
|
44
|
+
* La extensión de un archivo, si es una de las que el framework reconoce.
|
|
45
|
+
*
|
|
46
|
+
* Se compara en minúsculas: en macOS y Windows un `Index.TSX` es el mismo
|
|
47
|
+
* archivo, y rechazarlo por la capitalización sería un fallo incomprensible.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scriptExtensionOf(file: string): ScriptExtension | undefined;
|
|
50
|
+
export declare function isIgnoredModule(file: string, policy?: ExtensionPolicy): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* El archivo sin su extensión.
|
|
53
|
+
*
|
|
54
|
+
* Lanza en vez de devolver el path intacto: un archivo que llegó hasta acá sin
|
|
55
|
+
* una extensión reconocida es un error de configuración, y devolverlo tal cual
|
|
56
|
+
* lo convertiría en un fallo más adelante y más difícil de ubicar.
|
|
57
|
+
*/
|
|
58
|
+
export declare function stripScriptExtension(file: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Archivos que viven junto a las páginas sin ser una.
|
|
61
|
+
*
|
|
62
|
+
* Hace falta desde que la política acepta `.ts` y `.js`: un archivo suelto ahí
|
|
63
|
+
* es casi siempre un ayudante —un cliente de API, un tipo compartido, un test— y
|
|
64
|
+
* tomarlo por una ruta produce una URL que existe y falla al renderizar, porque
|
|
65
|
+
* no exporta ningún componente.
|
|
66
|
+
*/
|
|
67
|
+
export declare function pageExclusionOf(file: string, pagesDir?: string, exclusions?: readonly PageExclusion[]): PageExclusion | undefined;
|
|
68
|
+
export declare function isPageFile(file: string, policy?: ExtensionPolicy, pagesDir?: string, exclusions?: readonly PageExclusion[]): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* La ruta que un archivo de middleware protege.
|
|
71
|
+
*
|
|
72
|
+
* `pokedex/middlewares.tsx` protege `/pokedex`; `home/index.middlewares.js`
|
|
73
|
+
* protege `/home/index`. Antes esto se derivaba con una expresión que sólo
|
|
74
|
+
* recortaba `.ts` y `.js`, así que un middleware `.tsx` producía una ruta con la
|
|
75
|
+
* extensión adentro y no coincidía con ninguna página: no corría, y sin aviso.
|
|
76
|
+
*/
|
|
77
|
+
export declare function middlewareRoute(file: string, pagesDir?: string): string;
|
|
78
|
+
export declare function isMiddlewareFile(file: string, policy?: ExtensionPolicy): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Identificador de una página, sin su extensión ni el prefijo del directorio.
|
|
81
|
+
*
|
|
82
|
+
* Es la clave que comparten el catálogo de variantes y el manifest de rutas, así
|
|
83
|
+
* que dos archivos que sólo difieren en la extensión producen el mismo: es lo
|
|
84
|
+
* que permite detectar que ambos existen.
|
|
85
|
+
*/
|
|
86
|
+
export declare function pageIdOf(file: string, pagesDir?: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Dónde queda un módulo después de compilarse.
|
|
89
|
+
*
|
|
90
|
+
* El build emite siempre `.js`, cualquiera sea la extensión de origen, y sanea
|
|
91
|
+
* los corchetes de los parámetros porque no pueden formar parte de un nombre de
|
|
92
|
+
* archivo en todos los sistemas.
|
|
93
|
+
*/
|
|
94
|
+
export declare function toCompiledPath(file: string, policy?: ExtensionPolicy, pagesDir?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Dos archivos que producen la misma página son ambiguos.
|
|
97
|
+
*
|
|
98
|
+
* Cuál gana dependería del orden en que el sistema de archivos los devolvió, y
|
|
99
|
+
* ese orden cambia entre sistemas. Mejor fallar al arrancar nombrando los dos.
|
|
100
|
+
*/
|
|
101
|
+
export declare function assertNoModuleCollision(files: readonly string[], pagesDir?: string, options?: {
|
|
102
|
+
strict?: boolean;
|
|
103
|
+
}): void;
|
|
104
|
+
export declare function setExtensionPolicy(policy: ExtensionPolicy): void;
|
|
105
|
+
export declare function getExtensionPolicy(): ExtensionPolicy;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Qué extensiones acepta el framework, declarado una sola vez.
|
|
4
|
+
*
|
|
5
|
+
* Antes cada punto del pipeline tenía su propia idea: el descubrimiento globeaba
|
|
6
|
+
* `.tsx` y `.jsx`, el render exigía `.tsx` y lanzaba con cualquier otra cosa, y
|
|
7
|
+
* la carga en producción ramificaba por extensión. El resultado era que un
|
|
8
|
+
* proyecto en JavaScript descubría sus páginas y reventaba al renderizarlas.
|
|
9
|
+
*
|
|
10
|
+
* Ninguna función de este módulo toca el sistema de archivos: son puras sobre
|
|
11
|
+
* cadenas, y por eso se pueden probar sin montar un proyecto.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.DEFAULT_EXCLUSIONS = exports.DEFAULT_EXTENSION_POLICY = exports.SCRIPT_EXTENSIONS = void 0;
|
|
15
|
+
exports.globBrace = globBrace;
|
|
16
|
+
exports.pageGlob = pageGlob;
|
|
17
|
+
exports.middlewareGlobs = middlewareGlobs;
|
|
18
|
+
exports.scriptExtensionOf = scriptExtensionOf;
|
|
19
|
+
exports.isIgnoredModule = isIgnoredModule;
|
|
20
|
+
exports.stripScriptExtension = stripScriptExtension;
|
|
21
|
+
exports.pageExclusionOf = pageExclusionOf;
|
|
22
|
+
exports.isPageFile = isPageFile;
|
|
23
|
+
exports.middlewareRoute = middlewareRoute;
|
|
24
|
+
exports.isMiddlewareFile = isMiddlewareFile;
|
|
25
|
+
exports.pageIdOf = pageIdOf;
|
|
26
|
+
exports.toCompiledPath = toCompiledPath;
|
|
27
|
+
exports.assertNoModuleCollision = assertNoModuleCollision;
|
|
28
|
+
exports.setExtensionPolicy = setExtensionPolicy;
|
|
29
|
+
exports.getExtensionPolicy = getExtensionPolicy;
|
|
30
|
+
exports.SCRIPT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
|
|
31
|
+
exports.DEFAULT_EXTENSION_POLICY = {
|
|
32
|
+
page: exports.SCRIPT_EXTENSIONS,
|
|
33
|
+
middleware: exports.SCRIPT_EXTENSIONS,
|
|
34
|
+
compiled: '.js',
|
|
35
|
+
// Un `.d.ts` declara tipos: nunca es un módulo que se pueda ejecutar.
|
|
36
|
+
ignoredSuffixes: ['.d.ts'],
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Convenciones de archivos que pueden vivir bajo `app/pages` sin ser páginas.
|
|
40
|
+
*
|
|
41
|
+
* Este es el único registro: tanto el descubrimiento legado como el manifest
|
|
42
|
+
* consultan `isPageFile`, de modo que ampliar las extensiones no convierte un
|
|
43
|
+
* middleware, test o auxiliar en una ruta accidental.
|
|
44
|
+
*/
|
|
45
|
+
exports.DEFAULT_EXCLUSIONS = [
|
|
46
|
+
{ owner: 'middlewares', matches: (relative) => /(^|\/)middlewares$/.test(relative) },
|
|
47
|
+
{ owner: 'middlewares', matches: (relative) => /\.middlewares$/.test(relative) },
|
|
48
|
+
{ owner: 'auxiliar', matches: (relative) => /(^|\/)_[^/]*/.test(relative) },
|
|
49
|
+
{ owner: 'test', matches: (relative) => /\.(?:test|spec)$/.test(relative) },
|
|
50
|
+
];
|
|
51
|
+
/** `['.tsx','.ts']` → `{tsx,ts}`, la forma que entiende un glob. */
|
|
52
|
+
function globBrace(extensions) {
|
|
53
|
+
if (extensions.length === 0) {
|
|
54
|
+
throw new Error('[ress] La política de extensiones quedó vacía. ' +
|
|
55
|
+
`Declará al menos una de: ${exports.SCRIPT_EXTENSIONS.join(', ')}.`);
|
|
56
|
+
}
|
|
57
|
+
const bare = extensions.map((e) => e.slice(1));
|
|
58
|
+
return bare.length === 1 ? bare[0] : `{${bare.join(',')}}`;
|
|
59
|
+
}
|
|
60
|
+
function pageGlob(policy, pagesDir = 'app/pages') {
|
|
61
|
+
return `${pagesDir.replace(/\\/g, '/')}/**/*.${globBrace(policy.page)}`;
|
|
62
|
+
}
|
|
63
|
+
function middlewareGlobs(policy, pagesDir = 'app/pages') {
|
|
64
|
+
const ext = globBrace(policy.middleware);
|
|
65
|
+
const normalizedPagesDir = pagesDir.replace(/\\/g, '/');
|
|
66
|
+
return [`${normalizedPagesDir}/**/middlewares.${ext}`, `${normalizedPagesDir}/**/*.middlewares.${ext}`];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* La extensión de un archivo, si es una de las que el framework reconoce.
|
|
70
|
+
*
|
|
71
|
+
* Se compara en minúsculas: en macOS y Windows un `Index.TSX` es el mismo
|
|
72
|
+
* archivo, y rechazarlo por la capitalización sería un fallo incomprensible.
|
|
73
|
+
*/
|
|
74
|
+
function scriptExtensionOf(file) {
|
|
75
|
+
const lower = file.toLowerCase();
|
|
76
|
+
// De más larga a más corta: `.tsx` termina en `x`, no en `.ts`, pero el orden
|
|
77
|
+
// deja explícito que la comparación no es ambigua.
|
|
78
|
+
return exports.SCRIPT_EXTENSIONS.find((ext) => lower.endsWith(ext));
|
|
79
|
+
}
|
|
80
|
+
function isIgnoredModule(file, policy = exports.DEFAULT_EXTENSION_POLICY) {
|
|
81
|
+
const lower = file.toLowerCase();
|
|
82
|
+
return policy.ignoredSuffixes.some((suffix) => lower.endsWith(suffix.toLowerCase()));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* El archivo sin su extensión.
|
|
86
|
+
*
|
|
87
|
+
* Lanza en vez de devolver el path intacto: un archivo que llegó hasta acá sin
|
|
88
|
+
* una extensión reconocida es un error de configuración, y devolverlo tal cual
|
|
89
|
+
* lo convertiría en un fallo más adelante y más difícil de ubicar.
|
|
90
|
+
*/
|
|
91
|
+
function stripScriptExtension(file) {
|
|
92
|
+
const ext = scriptExtensionOf(file);
|
|
93
|
+
if (!ext) {
|
|
94
|
+
throw new Error(`[ress] "${file}" no tiene una extensión que el framework reconozca. ` +
|
|
95
|
+
`Válidas: ${exports.SCRIPT_EXTENSIONS.join(', ')}.`);
|
|
96
|
+
}
|
|
97
|
+
return file.slice(0, -ext.length);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Archivos que viven junto a las páginas sin ser una.
|
|
101
|
+
*
|
|
102
|
+
* Hace falta desde que la política acepta `.ts` y `.js`: un archivo suelto ahí
|
|
103
|
+
* es casi siempre un ayudante —un cliente de API, un tipo compartido, un test— y
|
|
104
|
+
* tomarlo por una ruta produce una URL que existe y falla al renderizar, porque
|
|
105
|
+
* no exporta ningún componente.
|
|
106
|
+
*/
|
|
107
|
+
function pageExclusionOf(file, pagesDir = 'app/pages', exclusions = exports.DEFAULT_EXCLUSIONS) {
|
|
108
|
+
const relative = stripScriptExtension(relativeToPages(file, pagesDir));
|
|
109
|
+
return exclusions.find((exclusion) => exclusion.matches(relative));
|
|
110
|
+
}
|
|
111
|
+
function isPageFile(file, policy = exports.DEFAULT_EXTENSION_POLICY, pagesDir = 'app/pages', exclusions = exports.DEFAULT_EXCLUSIONS) {
|
|
112
|
+
if (isIgnoredModule(file, policy))
|
|
113
|
+
return false;
|
|
114
|
+
const ext = scriptExtensionOf(file);
|
|
115
|
+
if (!ext || !policy.page.includes(ext))
|
|
116
|
+
return false;
|
|
117
|
+
return pageExclusionOf(file, pagesDir, exclusions) === undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* La ruta que un archivo de middleware protege.
|
|
121
|
+
*
|
|
122
|
+
* `pokedex/middlewares.tsx` protege `/pokedex`; `home/index.middlewares.js`
|
|
123
|
+
* protege `/home/index`. Antes esto se derivaba con una expresión que sólo
|
|
124
|
+
* recortaba `.ts` y `.js`, así que un middleware `.tsx` producía una ruta con la
|
|
125
|
+
* extensión adentro y no coincidía con ninguna página: no corría, y sin aviso.
|
|
126
|
+
*/
|
|
127
|
+
function middlewareRoute(file, pagesDir = 'app/pages') {
|
|
128
|
+
const relative = stripScriptExtension(relativeToPages(file, pagesDir));
|
|
129
|
+
return '/' + relative.replace(/\.middlewares$/, '').replace(/\/?middlewares$/, '');
|
|
130
|
+
}
|
|
131
|
+
function isMiddlewareFile(file, policy = exports.DEFAULT_EXTENSION_POLICY) {
|
|
132
|
+
if (isIgnoredModule(file, policy))
|
|
133
|
+
return false;
|
|
134
|
+
const ext = scriptExtensionOf(file);
|
|
135
|
+
if (!ext || !policy.middleware.includes(ext))
|
|
136
|
+
return false;
|
|
137
|
+
const name = normalize(file).split('/').pop() ?? '';
|
|
138
|
+
return name.startsWith('middlewares.') || name.includes('.middlewares.');
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Identificador de una página, sin su extensión ni el prefijo del directorio.
|
|
142
|
+
*
|
|
143
|
+
* Es la clave que comparten el catálogo de variantes y el manifest de rutas, así
|
|
144
|
+
* que dos archivos que sólo difieren en la extensión producen el mismo: es lo
|
|
145
|
+
* que permite detectar que ambos existen.
|
|
146
|
+
*/
|
|
147
|
+
function pageIdOf(file, pagesDir = 'app/pages') {
|
|
148
|
+
const relative = relativeToPages(file, pagesDir);
|
|
149
|
+
return stripScriptExtension(relative);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Dónde queda un módulo después de compilarse.
|
|
153
|
+
*
|
|
154
|
+
* El build emite siempre `.js`, cualquiera sea la extensión de origen, y sanea
|
|
155
|
+
* los corchetes de los parámetros porque no pueden formar parte de un nombre de
|
|
156
|
+
* archivo en todos los sistemas.
|
|
157
|
+
*/
|
|
158
|
+
function toCompiledPath(file, policy = exports.DEFAULT_EXTENSION_POLICY, pagesDir = 'app/pages') {
|
|
159
|
+
// `stripScriptExtension` lanza si no reconoce la extensión, que es lo correcto:
|
|
160
|
+
// devolver el nombre intacto producía `foo.mjs` → `foo.mjs.js`, un artefacto
|
|
161
|
+
// que no existe y cuyo fallo aparece recién al importarlo en producción.
|
|
162
|
+
const relative = stripScriptExtension(relativeToPages(file, pagesDir));
|
|
163
|
+
return relative.replace(/\[([^\]]+)\]/g, '_$1_') + policy.compiled;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Dos archivos que producen la misma página son ambiguos.
|
|
167
|
+
*
|
|
168
|
+
* Cuál gana dependería del orden en que el sistema de archivos los devolvió, y
|
|
169
|
+
* ese orden cambia entre sistemas. Mejor fallar al arrancar nombrando los dos.
|
|
170
|
+
*/
|
|
171
|
+
function assertNoModuleCollision(files, pagesDir = 'app/pages', options = {}) {
|
|
172
|
+
const byId = new Map();
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const id = pageIdOf(file, pagesDir);
|
|
175
|
+
const previous = byId.get(id);
|
|
176
|
+
if (previous) {
|
|
177
|
+
const message = `[ress] "${previous}" y "${file}" definen la misma página. ` +
|
|
178
|
+
'Cuál gana dependería del orden del sistema de archivos, que cambia entre ' +
|
|
179
|
+
'sistemas operativos. Dejá uno solo.';
|
|
180
|
+
// Al construir es un error; en desarrollo se avisa y se sigue con el
|
|
181
|
+
// primero, para no dejar el servidor caído mientras alguien renombra.
|
|
182
|
+
if (options.strict)
|
|
183
|
+
throw new Error(message);
|
|
184
|
+
console.warn(`${message}\nPor ahora responde "${previous}".`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
byId.set(id, file);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Los separadores de Windows se normalizan: el resto del framework usa `/`. */
|
|
191
|
+
function normalize(file) {
|
|
192
|
+
return file.replace(/\\/g, '/');
|
|
193
|
+
}
|
|
194
|
+
function relativeToPages(file, pagesDir) {
|
|
195
|
+
const normalized = normalize(file);
|
|
196
|
+
const marker = normalize(pagesDir).replace(/\/$/, '') + '/';
|
|
197
|
+
const index = normalized.indexOf(marker);
|
|
198
|
+
return index === -1 ? normalized.replace(/^\//, '') : normalized.slice(index + marker.length);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Política vigente del proceso.
|
|
202
|
+
*
|
|
203
|
+
* El descubrimiento corre en varios lugares que no reciben opciones —el
|
|
204
|
+
* catálogo, el escaneo de rutas, la carga de middlewares—, así que el plugin la
|
|
205
|
+
* fija una vez al resolver la configuración y todos leen la misma.
|
|
206
|
+
*/
|
|
207
|
+
let current = exports.DEFAULT_EXTENSION_POLICY;
|
|
208
|
+
function setExtensionPolicy(policy) {
|
|
209
|
+
current = policy;
|
|
210
|
+
}
|
|
211
|
+
function getExtensionPolicy() {
|
|
212
|
+
return current;
|
|
213
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PlatformInfo } from '../platform';
|
|
2
|
+
import type { CollectedHeadTag, PageConfig, ResolvedPageHead, RobotsConfig } from './types';
|
|
3
|
+
export declare function resolvePageHead(pageConfig: PageConfig | undefined, collected?: readonly CollectedHeadTag[], platform?: PlatformInfo | null): ResolvedPageHead;
|
|
4
|
+
export declare function robotsContent(value: RobotsConfig | undefined): string | undefined;
|
|
5
|
+
export declare function deduplicateHeadTags(tags: readonly CollectedHeadTag[]): CollectedHeadTag[];
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvePageHead = resolvePageHead;
|
|
4
|
+
exports.robotsContent = robotsContent;
|
|
5
|
+
exports.deduplicateHeadTags = deduplicateHeadTags;
|
|
6
|
+
const platform_tokens_1 = require("../variants/platform-tokens");
|
|
7
|
+
const resolve_1 = require("../variants/resolve");
|
|
8
|
+
const suffix_1 = require("../variants/suffix");
|
|
9
|
+
function resolvePageHead(pageConfig, collected = [], platform) {
|
|
10
|
+
const config = resolvePageConfigVariant(pageConfig, platform);
|
|
11
|
+
const tags = deduplicateHeadTags([...configToTags(config), ...collected]);
|
|
12
|
+
const robotsTag = [...tags].reverse().find((tag) => tag.type === 'meta' && String(tag.attrs.name ?? '').toLowerCase() === 'robots');
|
|
13
|
+
const robots = sanitizeRobotsValue(robotsTag?.attrs.content);
|
|
14
|
+
const normalizedTags = robotsTag && robots
|
|
15
|
+
? tags.map((tag) => tag === robotsTag
|
|
16
|
+
? { ...tag, attrs: { ...tag.attrs, content: robots } }
|
|
17
|
+
: tag)
|
|
18
|
+
: tags;
|
|
19
|
+
return {
|
|
20
|
+
tags: orderHeadTags(normalizedTags),
|
|
21
|
+
htmlAttributes: config.htmlAttributes ?? {},
|
|
22
|
+
bodyAttributes: config.bodyAttributes ?? {},
|
|
23
|
+
...(robots ? { robots } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function robotsContent(value) {
|
|
27
|
+
if (typeof value === 'string')
|
|
28
|
+
return sanitizeRobotsValue(value);
|
|
29
|
+
if (!value)
|
|
30
|
+
return undefined;
|
|
31
|
+
const directives = [];
|
|
32
|
+
if (value.index !== undefined)
|
|
33
|
+
directives.push(value.index ? 'index' : 'noindex');
|
|
34
|
+
if (value.follow !== undefined)
|
|
35
|
+
directives.push(value.follow ? 'follow' : 'nofollow');
|
|
36
|
+
if (value.noarchive)
|
|
37
|
+
directives.push('noarchive');
|
|
38
|
+
if (value.nosnippet)
|
|
39
|
+
directives.push('nosnippet');
|
|
40
|
+
if (value.noimageindex)
|
|
41
|
+
directives.push('noimageindex');
|
|
42
|
+
if (value.notranslate)
|
|
43
|
+
directives.push('notranslate');
|
|
44
|
+
return directives.length ? directives.join(', ') : undefined;
|
|
45
|
+
}
|
|
46
|
+
function sanitizeRobotsValue(value) {
|
|
47
|
+
if (value == null)
|
|
48
|
+
return undefined;
|
|
49
|
+
const normalized = String(value).replace(/[\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
50
|
+
return normalized || undefined;
|
|
51
|
+
}
|
|
52
|
+
function resolvePageConfigVariant(pageConfig, platform) {
|
|
53
|
+
if (!pageConfig)
|
|
54
|
+
return {};
|
|
55
|
+
const { variants, ...base } = pageConfig;
|
|
56
|
+
if (!platform || !variants || Object.keys(variants).length === 0)
|
|
57
|
+
return base;
|
|
58
|
+
const available = [
|
|
59
|
+
{ suffix: '', tags: [] },
|
|
60
|
+
...Object.keys(variants).sort().map((suffix) => ({
|
|
61
|
+
suffix,
|
|
62
|
+
tags: (0, suffix_1.parseVariantSuffix)(suffix, platform_tokens_1.defaultPlatformRegistry),
|
|
63
|
+
})),
|
|
64
|
+
];
|
|
65
|
+
const selected = (0, resolve_1.resolveVariant)(platform, available, platform_tokens_1.defaultPlatformRegistry);
|
|
66
|
+
return selected.suffix ? mergeConfig(base, variants[selected.suffix] ?? {}) : base;
|
|
67
|
+
}
|
|
68
|
+
function mergeConfig(base, variant) {
|
|
69
|
+
return {
|
|
70
|
+
...base,
|
|
71
|
+
...variant,
|
|
72
|
+
htmlAttributes: { ...base.htmlAttributes, ...variant.htmlAttributes },
|
|
73
|
+
bodyAttributes: { ...base.bodyAttributes, ...variant.bodyAttributes },
|
|
74
|
+
head: {
|
|
75
|
+
...base.head,
|
|
76
|
+
...variant.head,
|
|
77
|
+
meta: [...(base.head?.meta ?? []), ...(variant.head?.meta ?? [])],
|
|
78
|
+
link: [...(base.head?.link ?? []), ...(variant.head?.link ?? [])],
|
|
79
|
+
script: [...(base.head?.script ?? []), ...(variant.head?.script ?? [])],
|
|
80
|
+
style: [...(base.head?.style ?? []), ...(variant.head?.style ?? [])],
|
|
81
|
+
noscript: [...(base.head?.noscript ?? []), ...(variant.head?.noscript ?? [])],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function configToTags(config) {
|
|
86
|
+
const head = config.head;
|
|
87
|
+
if (!head)
|
|
88
|
+
return [];
|
|
89
|
+
const tags = [];
|
|
90
|
+
const robots = robotsContent(head.robots);
|
|
91
|
+
if (head.title !== undefined)
|
|
92
|
+
tags.push({ type: 'title', attrs: {}, content: head.title });
|
|
93
|
+
if (robots)
|
|
94
|
+
tags.push({ type: 'meta', attrs: { name: 'robots', content: robots } });
|
|
95
|
+
for (const attrs of head.meta ?? [])
|
|
96
|
+
tags.push({ type: 'meta', attrs });
|
|
97
|
+
for (const attrs of head.link ?? [])
|
|
98
|
+
tags.push({ type: 'link', attrs });
|
|
99
|
+
for (const { content, ...attrs } of head.script ?? [])
|
|
100
|
+
tags.push({ type: 'script', attrs, content });
|
|
101
|
+
for (const { content, ...attrs } of head.style ?? [])
|
|
102
|
+
tags.push({ type: 'style', attrs, content });
|
|
103
|
+
if (head.base)
|
|
104
|
+
tags.push({ type: 'base', attrs: head.base });
|
|
105
|
+
for (const item of head.noscript ?? [])
|
|
106
|
+
tags.push({ type: 'noscript', attrs: {}, content: item.content });
|
|
107
|
+
return tags;
|
|
108
|
+
}
|
|
109
|
+
function deduplicateHeadTags(tags) {
|
|
110
|
+
const output = [];
|
|
111
|
+
const positions = new Map();
|
|
112
|
+
for (const tag of tags) {
|
|
113
|
+
const key = dedupKey(tag);
|
|
114
|
+
if (!key) {
|
|
115
|
+
output.push(tag);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const previous = positions.get(key);
|
|
119
|
+
if (previous === undefined) {
|
|
120
|
+
positions.set(key, output.length);
|
|
121
|
+
output.push(tag);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
output[previous] = tag;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
}
|
|
129
|
+
function dedupKey(tag) {
|
|
130
|
+
if (tag.type === 'title' || tag.type === 'base')
|
|
131
|
+
return tag.type;
|
|
132
|
+
if (tag.type === 'meta') {
|
|
133
|
+
if (tag.attrs.name != null)
|
|
134
|
+
return `meta:name:${String(tag.attrs.name).toLowerCase()}`;
|
|
135
|
+
if (tag.attrs.property != null)
|
|
136
|
+
return `meta:property:${String(tag.attrs.property).toLowerCase()}`;
|
|
137
|
+
if (tag.attrs.charset != null || tag.attrs.charSet != null)
|
|
138
|
+
return 'meta:charset';
|
|
139
|
+
}
|
|
140
|
+
if (tag.type === 'script' && tag.attrs.src != null)
|
|
141
|
+
return `script:${String(tag.attrs.src)}`;
|
|
142
|
+
if (tag.type === 'link' && String(tag.attrs.rel).toLowerCase() === 'canonical')
|
|
143
|
+
return 'link:canonical';
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
function orderHeadTags(tags) {
|
|
147
|
+
return tags
|
|
148
|
+
.map((tag, index) => ({ tag, index, rank: tagRank(tag) }))
|
|
149
|
+
.sort((a, b) => a.rank - b.rank || a.index - b.index)
|
|
150
|
+
.map(({ tag }) => tag);
|
|
151
|
+
}
|
|
152
|
+
function tagRank(tag) {
|
|
153
|
+
if (tag.type === 'meta' && (tag.attrs.charset != null || tag.attrs.charSet != null))
|
|
154
|
+
return 0;
|
|
155
|
+
if (tag.type === 'meta' && String(tag.attrs.name).toLowerCase() === 'viewport')
|
|
156
|
+
return 1;
|
|
157
|
+
if (tag.type === 'link' && ['preload', 'modulepreload'].includes(String(tag.attrs.rel).toLowerCase()))
|
|
158
|
+
return 2;
|
|
159
|
+
if (tag.type === 'link' && String(tag.attrs.rel).toLowerCase() === 'stylesheet')
|
|
160
|
+
return 3;
|
|
161
|
+
if (tag.type === 'title')
|
|
162
|
+
return 4;
|
|
163
|
+
if (tag.type === 'meta')
|
|
164
|
+
return 5;
|
|
165
|
+
return 6;
|
|
166
|
+
}
|