(html): Añade Preload para precarga de recursos

Nuevo tipo `Preload` (junto a `StyleSheet` y `JavaScript`) que emite
`<link rel="preload">` con soporte para fuentes, estilos, scripts e
imágenes (WebP y AVIF incluidos). Bootsier lo usa para precargar las
fuentes Source Sans 3 y eliminar el FOUT en la carga inicial.
This commit is contained in:
Manuel Cillero 2026-07-07 00:03:36 +02:00
parent 9b2d430d8b
commit c7b680a7f7
6 changed files with 232 additions and 3 deletions

View file

@ -3,7 +3,7 @@ use crate::core::TypeInfo;
use crate::core::component::{ChildOp, Component, MessageLevel, StatusMessage};
use crate::core::theme::all::DEFAULT_THEME;
use crate::core::theme::{ChildrenInRegions, DefaultRegion, RegionRef, TemplateRef, ThemeRef};
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
use crate::html::{Assets, Favicon, JavaScript, Preload, StyleSheet};
use crate::html::{Markup, Props, PropsOp, RoutePath, html};
use crate::locale::L10n;
use crate::locale::{LangId, LanguageIdentifier, RequestLocale};
@ -23,6 +23,11 @@ pub enum AssetsOp {
/// Define el *favicon* solo si no se ha establecido previamente.
SetFaviconIfNone(Favicon),
/// Añade un recurso para precarga al documento.
AddPreload(Preload),
/// Elimina un recurso para precarga por su ruta.
RemovePreload(&'static str),
/// Añade una hoja de estilos CSS al documento.
AddStyleSheet(StyleSheet),
/// Elimina una hoja de estilos por su ruta o identificador.
@ -312,6 +317,7 @@ pub struct Context {
theme : ThemeRef, // Referencia al tema usado para renderizar.
template : TemplateRef, // Plantilla usada para renderizar.
favicon : Option<Favicon>, // Favicon, si se ha definido.
preloads : Assets<Preload>, // Recursos para precarga.
stylesheets : Assets<StyleSheet>, // Hojas de estilo CSS.
javascripts : Assets<JavaScript>, // Scripts JavaScript.
body_props : Props, // Id, clases CSS y atributos del <body>.
@ -343,6 +349,7 @@ impl Context {
theme : *DEFAULT_THEME,
template : DEFAULT_THEME.default_template(),
favicon : None,
preloads : Assets::<Preload>::new(),
stylesheets: Assets::<StyleSheet>::new(),
javascripts: Assets::<JavaScript>::new(),
body_props : Props::default(),
@ -370,6 +377,7 @@ impl Context {
// Extrae temporalmente los recursos.
let favicon = mem_take(&mut self.favicon); // Deja valor por defecto (None) en self.
let preloads = mem_take(&mut self.preloads); // Assets<Preload>::default() en self.
let stylesheets = mem_take(&mut self.stylesheets); // Assets<StyleSheet>::default() en self.
let javascripts = mem_take(&mut self.javascripts); // Assets<JavaScript>::default() en self.
@ -378,12 +386,15 @@ impl Context {
@if let Some(fi) = &favicon {
(fi.render(self))
}
// Primero los recursos para precarga para iniciar las descargas inmediatamente.
(preloads.render(self))
(stylesheets.render(self))
(javascripts.render(self))
};
// Restaura los campos tal y como estaban.
self.favicon = favicon;
self.preloads = preloads;
self.stylesheets = stylesheets;
self.javascripts = javascripts;
@ -549,6 +560,13 @@ impl Contextual for Context {
self.favicon = Some(icon);
}
}
// Preloads.
AssetsOp::AddPreload(preload) => {
self.preloads.add(preload);
}
AssetsOp::RemovePreload(path) => {
self.preloads.remove(path);
}
// Stylesheets.
AssetsOp::AddStyleSheet(css) => {
self.stylesheets.add(css);