Skip to content

Assets pt BR

Edgar Mesquita edited this page Aug 14, 2026 · 2 revisions

Sistema de gestão de assets

🌐 Esta página em: English · Português

O eQuantic.UI oferece um sistema declarativo de dependências de assets que permite aos componentes declararem os scripts e folhas de estilo de que precisam. Os assets são coletados automaticamente durante o SSR, deduplicados e injetados no <head> da página.

Visão geral

O sistema resolve um problema comum: componentes que dependem de bibliotecas externas (Prism.js, Chart.js, etc.) precisam que os scripts/CSS deles sejam carregados, mas não deveriam embutir tags <script> inline no HTML renderizado. Em vez disso, eles declaram dependências, e o framework cuida da injeção.

Percurso da árvore de componentes → Coleta IRequireAssets → Coleta IComponentAssetProvider<T> → Deduplica → Injeta no <head>

Tipos principais

Todos os tipos estão em eQuantic.UI.Core.Assets.

IAsset

Interface base para todos os tipos de asset.

public interface IAsset
{
    string Key { get; }      // Chave única para deduplicação
    string? Id { get; }      // Id HTML opcional para manipulação no cliente
    string Render();         // Renderiza como tag HTML
}

Implementações de asset

Tipo Saída Formato da chave
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 os tipos aceitam um parâmetro Id opcional para manipulação do DOM no 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">

AssetBuilder

Builder fluente passado aos componentes para declarar dependências:

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);
}

AssetCollection

Coleta e deduplica os assets. A ordem de renderização é otimizada: CSS primeiro, depois JS (a ordem correta de bloqueio de render).

StylesheetAsset → InlineStyleAsset → ScriptAsset → InlineScriptAsset

A deduplicação usa TryAdd com a Key do asset, então o primeiro registro vence.

Dois padrões

Padrão 1: IRequireAssets (autodeclarante)

Para componentes que são donos das próprias dependências. O componente declara ele mesmo o que precisa.

public class CodeBlock : StatelessComponent, IRequireAssets
{
    public void ConfigureAssets(AssetBuilder assets)
    {
        assets.AddStylesheet(
            "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
            id: "prism-theme");
        assets.AddScript("https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js");
    }
}

Quando usar:

  • Componentes do framework (CodeBlock, gráficos, etc.)
  • Componentes cujas dependências são intrínsecas
  • O desenvolvedor que usa o componente não precisa saber das bibliotecas por baixo

Padrão 2: IComponentAssetProvider<T> (provedor externo)

Para associar assets a componentes externamente, tipicamente componentes de terceiros que não implementam 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 (escolha um):

// Opção 1: varredura automática (descoberto sozinho nos assemblies varridos)
options.ScanAssembly(typeof(Program).Assembly);

// Opção 2: registro explícito via UIOptions
options.WithAssetProvider<ChartJsAssetProvider>();

// Opção 3: registro manual na DI
services.AddSingleton<IComponentAssetProvider<ChartCanvas>, ChartJsAssetProvider>();

Quando usar:

  • Componentes de terceiros que você não pode modificar
  • Sobreposições no nível do app para assets de componentes do framework
  • Componentes vindos de pacotes externos

Prioridade

Quando os dois padrões existem para o mesmo tipo de componente:

  1. O IRequireAssets executa primeiro (padrão do componente)
  2. O IComponentAssetProvider<T> executa depois (provedor externo)

A deduplicação é por Key, com semântica de o primeiro vence. Se você precisa que o provedor sobreponha o asset padrão de um componente, use uma Key diferente (por exemplo, outra URL).

Como funciona (pipeline de SSR)

Durante a renderização no servidor, o ServerRenderingService percorre a árvore de componentes:

RenderPageAsync()
    ├── Cria a AssetCollection
    ├── CollectAssets(rootComponent, assets, services, visited)
    │     ├── Verifica IRequireAssets → ConfigureAssets()
    │     ├── Verifica a DI por IComponentAssetProvider<T> → ConfigureAssets()
    │     ├── Recursa em component.Children
    │     └── Para StatelessComponent → Build() → recursa no resultado
    ├── Renderiza o HTML
    └── Devolve o ServerRenderResult com os Assets

Os assets são então mesclados em HtmlShellOptions.HeadTags antes de servir a página.

Comportamentos-chave:

  • Deduplicação por tipo: cada tipo de componente é processado uma vez (via HashSet<Type>)
  • Deduplicação por asset: cada chave de asset é registrada uma vez (via Dictionary.TryAdd)
  • Custo zero: páginas sem componentes que exigem assets não ganham tag extra nenhuma
  • Degradação graciosa: se o Build() falhar (por exemplo, DI faltando), o componente é pulado

Registro automático

As implementações de IComponentAssetProvider<T> são descobertas automaticamente durante o AddUI():

builder.Services.AddUI(options =>
{
    options.ScanAssembly(typeof(Program).Assembly);  // Acha os provedores aqui sozinho
});

A varredura procura todas as classes não abstratas que implementam IComponentAssetProvider<T> nos assemblies varridos e as registra como singletons via TryAddSingleton.

Registros explícitos via WithAssetProvider<T>() têm prioridade sobre os varridos automaticamente (são registrados primeiro).

Exemplo do mundo real: CodeBlock

O componente CodeBlock demonstra o padrão completo:

public class CodeBlock : StatelessComponent, IRequireAssets
{
    public void ConfigureAssets(AssetBuilder assets)
    {
        // Folha de estilo com id para troca dinâmica de tema
        assets.AddStylesheet(
            "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css",
            id: "prism-theme");

        // Scripts principais do 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");

        // Funções utilitárias + troca automática de tema claro/escuro
        assets.AddInlineScript(
            "function copyToClipboard(id){...}" +
            "function toggleCodeBlock(id){...}" +
            "(function(){" +
                "function updateTheme(){...}" +    // Alterna entre prism.css e prism-tomorrow.css
                "updateTheme();" +
                "new MutationObserver(function(){updateTheme()})" +
                    ".observe(document.documentElement,{attributes:true,attributeFilter:['class']});" +
            "})();"
        );
    }
}

O desenvolvedor só usa new CodeBlock(code, "csharp"), e os scripts do Prism.js, o CSS, a troca de tema e as funções utilitárias são todos tratados automaticamente.

Desde 0.2.0-preview.1

O ícone do app, desenhado em C# (IAppIcon)

O ícone do lançador é um componente como qualquer outro, no mesmo vocabulário, então ele não consegue divergir da marca com que o app já desenha:

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());
}

Duas cercas, ambas vindas do meio e não do framework:

  • Preencha o quadrado inteiro, opaco. Um ícone transparente é composto contra o que quer que o lançador esteja mostrando, que nunca é aquilo contra o que você desenhou.
  • Sem estado, sem interação. Ele é construído uma vez; um handler de toque num ícone é um handler que ninguém alcança. Fique em formas, gradientes e no máximo uma letra ou duas, porque no tamanho em que uma tela inicial de fato mostra, qualquer coisa mais fina vira borrão.

O build o rasteriza em cada tamanho que cada plataforma quer, e liga os do web no head sem o app precisar dizer nada.

Clone this wiki locally