Sistema de gestión de assetseQuantic.UI provee un sistema declarativo de dependencias de assets que permite a los componentes declarar los scripts y las hojas de estilos que necesitan. Los assets se recogen automáticamente durante el SSR, se deduplican y se inyectan en el <head> de la página.
El sistema resuelve un problema común: los componentes que dependen de bibliotecas externas (Prism.js, Chart.js, etc.) necesitan que sus scripts y su CSS se carguen, pero no deberían incrustar etiquetas <script> en línea en el HTML que renderizan. En lugar de eso, declaran sus dependencias, y el framework se encarga de la inyección.
Recorrido del árbol de componentes → Recoger IRequireAssets → Recoger IComponentAssetProvider<T> → Deduplicar → Inyectar en <head>
Todos los tipos están en eQuantic.UI.Server.Assets.
La interfaz base de todos los tipos de asset.
string Key { get; } // Clave única para la deduplicación
string? Id { get; } // Id HTML opcional, para manipular desde el cliente
string Render(); // Se renderiza como etiqueta HTML
Implementaciones de asset
Tipo
Salida
Formato de la clave
ScriptAsset
<script src="..." defer></script>
script:{Src}
InlineScriptAsset
<script>...</script>
inline-script:{hash}
StylesheetAsset
<link rel="stylesheet" href="...">
stylesheet:{Href}
InlineStyleAsset
<style>...</style>
inline-style:{hash}
Todos los tipos admiten un parámetro Id opcional para manipular el DOM desde el cliente:
new StylesheetAsset("https://cdn.example.com/theme.css", Id: "theme-css")
// Renderiza: <link rel="stylesheet" href="https://cdn.example.com/theme.css" id="theme-css">
El builder fluido que se pasa a los componentes para declarar dependencias:
public class AssetBuilder
AssetBuilder AddScript(string src, bool defer = true, string? id = null);
AssetBuilder AddInlineScript(string content, string? id = null);
AssetBuilder AddStylesheet(string href, string? id = null);
AssetBuilder AddInlineStyle(string content, string? id = null);
Recoge y deduplica los assets. El orden de renderizado está optimizado: primero el CSS, luego el JS (el orden correcto de bloqueo del render).
StylesheetAsset → InlineStyleAsset → ScriptAsset → InlineScriptAsset
La deduplicación usa TryAdd con la Key del asset, así que gana el primer registro.
Patrón 1: IRequireAssets (autodeclarativo)
Para los componentes que son dueños de sus dependencias. El componente mismo declara lo que necesita.
public class CodeBlock : StatelessComponent, IRequireAssets
public void ConfigureAssets(AssetBuilder assets)
"https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js");
Cuándo usarlo:
•
Componentes del framework (CodeBlock, gráficos, etc.)
•
Componentes cuyas dependencias son intrínsecas
•
Cuando quien usa el componente no necesita saber de las bibliotecas subyacentes
Patrón 2: IComponentAssetProvider\<T\> (proveedor externo)
Para asociar assets a componentes desde fuera, típicamente componentes de terceros que no implementan IRequireAssets.
public class ChartJsAssetProvider : IComponentAssetProvider<ChartCanvas>
public void ConfigureAssets(AssetBuilder assets)
assets.AddScript("https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js");
Registro (elige uno):
// Opción 1: escaneo automático (se descubren solos en los ensamblados escaneados)
options.ScanAssembly(typeof(Program).Assembly);
// Opción 2: registro explícito vía UIOptions
options.WithAssetProvider<ChartJsAssetProvider>();
// Opción 3: registro manual en la DI
services.AddSingleton<IComponentAssetProvider<ChartCanvas>, ChartJsAssetProvider>();
Cuándo usarlo:
•
Componentes de terceros que no puedes modificar
•
Sobrescrituras a nivel de aplicación de los assets de un componente del framework
•
Componentes que vienen de paquetes externos
Cuando existen los dos patrones para el mismo tipo de componente:
1.
IRequireAssets se ejecuta primero (el valor por defecto del componente)
2.
IComponentAssetProvider<T> se ejecuta después (el proveedor externo)
La deduplicación es por Key con semántica de gana el primero. Si necesitas que el proveedor sobrescriba el asset por defecto de un componente, usa una Key distinta (por ejemplo, otra URL).
Cómo funciona (la pipeline del SSR)
Durante el renderizado en el servidor, ServerRenderingService recorre el árbol de componentes:
├── Crea el AssetCollection
├── CollectAssets(rootComponent, assets, services, visited)
│ ├── Comprueba IRequireAssets → ConfigureAssets()
│ ├── Comprueba la DI por IComponentAssetProvider<T> → ConfigureAssets()
│ ├── Recurre en component.Children
│ └── Para un StatelessComponent → Build() → recurre en el resultado
└── Devuelve un ServerRenderResult con los Assets
Los assets se fusionan luego en HtmlShellOptions.HeadTags antes de servir la página.
Comportamientos clave:
•
Deduplicación por tipo: cada tipo de componente se procesa una vez (vía HashSet<Type>)
•
Deduplicación por asset: cada clave de asset se registra una vez (vía Dictionary.TryAdd)
•
Cero sobrecoste: las páginas sin componentes que pidan assets no reciben etiquetas extra
•
Degradación elegante: si Build() falla (por ejemplo, por una DI que falta), el componente se salta
Las implementaciones de IComponentAssetProvider<T> se descubren solas durante AddUI():
builder.Services.AddUI(options =>
options.ScanAssembly(typeof(Program).Assembly); // Encuentra aquí los proveedores
El escaneo busca todas las clases no abstractas que implementan IComponentAssetProvider<T> en los ensamblados escaneados y las registra como singletons vía TryAddSingleton.
Los registros explícitos vía WithAssetProvider<T>() tienen prioridad sobre los escaneados automáticamente (se registran primero).
Ejemplo del mundo real: CodeBlock
El componente CodeBlock demuestra el patrón completo:
public class CodeBlock : StatelessComponent, IRequireAssets
public void ConfigureAssets(AssetBuilder assets)
// Hoja de estilos con id, para cambiar el tema dinámicamente
"https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
// Los scripts centrales de Prism.js
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js");
assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js");
// Funciones de utilidad + cambio automático entre tema claro y oscuro
"function copyToClipboard(id){...}" +
"function toggleCodeBlock(id){...}" +
"function updateTheme(){...}" + // Alterna entre prism.css y prism-tomorrow.css
"new MutationObserver(function(){updateTheme()})" +
".observe(document.documentElement,{attributes:true,attributeFilter:['class']});" +
Quien programa solo usa new CodeBlock(code, "csharp"), y los scripts de Prism.js, el CSS, el cambio de tema y las funciones de utilidad se manejan todos automáticamente.
Desde 0.2.0-preview.1
El icono de la aplicación, dibujado en C# (IAppIcon)
El icono del lanzador es un componente como cualquier otro, en el mismo vocabulario, así que no puede desviarse de la marca con la que la aplicación ya dibuja:
public sealed class AppIcon : IAppIcon
public VisualNode Build(ComponentContext context) =>
Box(new BoxStyle { Background = Brand, CornerRadius = new CornerRadii(0) },
Text("eQ", TypeRole.Display, Ink).Centered());
Dos vallas, las dos del medio y no del framework:
•
Llena el cuadrado entero, opaco. Un icono transparente se compone contra lo que el lanzador esté mostrando, que nunca es aquello contra lo que diseñaste.
•
Sin estado, sin interacción. Se construye una vez; un manejador de presión en un icono es un manejador al que nadie puede llegar. Limítalo a formas, degradados y a lo sumo una o dos letras, porque al tamaño en que una pantalla de inicio lo muestra de verdad, cualquier cosa más fina es un borrón.
El build lo rasteriza a cada tamaño que quiere cada plataforma, y enlaza los de la web en el head sin que la aplicación lo diga.