eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
Docspt-BR
Fluxo de build do eQuantic.UI
Edit this page
3 min read
🌐 Esta página em: English · Português
Este documento descreve o fluxo de build do eQuantic.UI, demonstrando como o framework mantém zero dependências externas para o consumidor.
Fluxo visual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
┌─────────────────────────────────────────────────────────────────────────────┐
│ DESENVOLVIMENTO (árvore de código) │
└─────────────────────────────────────────────────────────────────────────────┘
reconciler.ts, component.ts, etc.
┌──────────────────────────────────┐
│ npm run build │ (só durante o desenvolvimento)
│ (eQuantic.UI.Runtime) │
└──────────────────────────────────┘
dist/index.js (runtime compilado)
boot.ts ──────importa─────────┘
┌──────────────────────────────────┐
│ dotnet build │
│ (eQuantic.UI.Server) │
│ │
│ Target ResolveBunForServer: │
│ ├─ Procura o Bun em: │
│ │ Runtime.Osx64/tools/bun/ │
│ │ Runtime.Win64/tools/bun/ │
│ │ Runtime.Linux64/tools/bun/ │
│ ├─ Extrai do .zip se preciso │
│ └─ chmod +x (Unix) │
│ │
│ Target BundleRuntime: │
│ └─ "$(_BunPath)" build boot.ts │
└──────────────────────────────────┘
wwwroot/runtime.js (embarcado no Server.dll)
┌──────────────────────────────────┐
│ dotnet pack │
│ (eQuantic.UI.Server) │
└──────────────────────────────────┘
artifacts/packages/eQuantic.UI.Server.0.1.1.nupkg
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONSUMIDOR (projeto cliente) │
└─────────────────────────────────────────────────────────────────────────────┘
MyApp.csproj
├─ Sdk="eQuantic.UI.Sdk/0.1.1"
└─ PackageReference: eQuantic.UI.Server, eQuantic.UI.Runtime
┌──────────────────────────────────┐
│ dotnet restore │
│ │
│ O NuGet instala os pacotes: │
│ ├─ eQuantic.UI.Sdk │
│ ├─ eQuantic.UI.Server │
│ ├─ eQuantic.UI.Runtime │
│ │ └─ (metapacote) │
│ └─ eQuantic.UI.Runtime.Osx64 │ ◄── o Bun vem embarcado aqui!
│ └─ tools/bun/bun-darwin.zip │
└──────────────────────────────────┘
> **Qual Bun?** O `src/eQuantic.UI.Runtime/bun-toolchain.json` guarda a versão e um SHA-256 por
> plataforma, conferidos a cada execução dos testes. Os digests provam que os bytes comitados são os
> que o release publicou; a versão é verificada extraindo o binário do host e rodando ele, porque um
> manifesto que ninguém compara com aquilo que ele descreve acaba se afastando dele.
>
> Os seis pacotes de plataforma (`Osx64`, `OsxArm64`, `Win64`, `WinArm64`, `Linux64`, `LinuxArm64`)
> carregam o mesmo build; um Bun diferente por arquitetura é como se consegue um bundle que só falha
> na máquina de uma pessoa.
>
> Para atualizar: troque os arquivos `.zip`, rode os testes uma vez com `EQ_UPDATE_BUN_MANIFEST=1`, e
> leia o diff. Uma versão que mudou com digests que não mudaram é um erro, e o contrário também.
┌──────────────────────────────────┐
│ dotnet build │
│ │
│ O SDK.targets executa: │
│ │
│ 1. ResolveBunZipPath │
│ └─ $(PkgeQuantic_UI_Runtime_ │
│ Osx64)/tools/bun/*.zip │
│ │
│ 2. EnsureBunExtracted │
│ ├─ Descompacta se preciso │
│ └─ chmod +x (Unix) │
│ │
│ 3. ResolveBunPath │
│ └─ Define $(BunPath) │
│ │
│ 4. InstallBunPackages │
│ ├─ <BunPackage> → bun add │
│ └─ Symlink node_modules │
│ │
│ 5. CompileEQuanticUI │
│ └─ dotnet eqc.dll ... --bun │
│ "$(BunPath)" │
│ │
│ 6. CopyEQuanticRuntime │ ◄── entrega do runtime.js
│ └─ Copia do pacote Runtime │
│ para wwwroot/_equantic/ │
│ │
│ └─ "$(BunPath)" x │
└──────────────────────────────────┘
wwwroot/_equantic/
├─ runtime.js (do pacote Runtime)
└─ *.js (componentes compilados)
┌──────────────────────────────────┐
│ dotnet run │
│ │
│ O servidor serve: │
│ ├─ runtime.js (do Server.dll) │
│ └─ *.js (de wwwroot/_equantic) │
└──────────────────────────────────┘
O browser carrega a aplicação
Origem do Bun por componente
Componente
Origem do Bun
Server (build do pacote)
eQuantic.UI.Runtime.{OS}/tools/bun/ (árvore de código)
SDK (consumidor)
$(PkgeQuantic_UI_Runtime_{OS})/tools/bun/ (cache do NuGet)
Requisitos do consumidor
O consumidor só precisa de:
.NET SDK 10.0
dotnet restore + dotnet build
Nenhuma instalação de Node.js, npm ou Bun global é necessária.
Arquivos-chave
Arquivo
Responsabilidade
Sdk/Sdk.targets
Resolve o Bun, instala os itens <BunPackage>, compila os componentes
Server.csproj
Resolve o Bun na árvore de código, empacota o runtime.js
Runtime.{OS}.csproj
Empacota o executável do Bun para cada plataforma
Targets do MSBuild (ordem de execução)
No SDK (consumidor)
1.
ResolveBunZipPath - encontra o .zip do Bun no cache do NuGet
2.
EnsureBunExtracted - extrai o executável se preciso
3.
ResolveBunPath - define $(BunPath) para uso posterior
4.
InstallBunPackages - instala os itens <BunPackage> via bun add (veja BunPackage)
5.
CompileEQuanticUI - transpila C# → TypeScript → JavaScript
6.
CopyEQuanticRuntime - copia o runtime.js do pacote Runtime para wwwroot/\_equantic/
No Server (desenvolvimento)
1.
ResolveBunForServer - encontra o Bun na árvore de código
2.
BundleRuntime - compila boot.ts → runtime.js
Arquitetura de pacotes e autocontenção
O eQuantic.UI segue uma arquitetura de pacotes autocontidos, em que cada pacote gerencia os próprios artefatos. O SDK age como orquestrador, referenciando os outros pacotes pelas propriedades $(Pkg*) do NuGet.
Princípios da arquitetura
Antes (problemático):
1
2
3
Pacote SDK ❌
├─ runtime.js embarcado (copiado do Runtime)
└─ arquivos *.cs embarcados (copiados do Components)
Problemas: acoplamento forte, conflitos de versão, duplicação de artefatos
Depois (correto):
1
2
3
4
5
6
7
8
Pacote Runtime ✅
└─ tools/runtime/runtime.js (autocontido)
Pacote Components ✅
└─ tools/source/*.cs (autocontido)
Pacote SDK ✅
└─ Referencia os outros pacotes via $(PkgeQuantic_UI_*)
Benefícios: desacoplamento, versionamento correto, sem duplicação
Entrega do runtime.js
1. Empacotamento (desenvolvimento)
Durante o dotnet pack do eQuantic.UI.Runtime:
1
2
3
4
5
<!-- eQuantic.UI.Runtime.csproj -->
<ItemGroup>
<Content Include="dist\index.js" PackagePath="tools\runtime\runtime.js"
Condition="Exists('dist\index.js')" />
</ItemGroup>
O pacote Runtime embarca o próprio artefato JavaScript compilado.
2. Entrega (build do consumidor)
Durante o dotnet build, o target CopyEQuanticRuntime do SDK executa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Sdk/Sdk.targets -->
<Target Name="CopyEQuanticRuntime" AfterTargets="CompileEQuanticUI"
Condition="'$(EnableEQuanticUICompilation)' == 'true'">
<PropertyGroup>
<!-- Resolve a partir do pacote Runtime pela propriedade do NuGet -->
<_RuntimeSourcePath Condition="'$(PkgeQuantic_UI_Runtime)' != ''">
$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js
</_RuntimeSourcePath>
<!-- Alternativa pela árvore de código (só em desenvolvimento) -->
<_RuntimeSourcePath Condition="'$(_RuntimeSourcePath)' == ''">
$(MSBuildThisFileDirectory)../../eQuantic.UI.Runtime/dist/index.js
</_RuntimeSourcePath>
<_RuntimeDestPath>$(MSBuildProjectDirectory)/$(EQuanticOutputPath)runtime.js</_RuntimeDestPath>
</PropertyGroup>
<Error Text="eQuantic.UI: Runtime not found. Ensure eQuantic.UI.Runtime package is installed."
Condition="'$(_RuntimeSourcePath)' == '' Or !Exists('$(_RuntimeSourcePath)')" />
<Copy SourceFiles="$(_RuntimeSourcePath)" DestinationFiles="$(_RuntimeDestPath)" />
</Target>
Entrega das fontes do Components
1. Empacotamento (desenvolvimento)
Durante o dotnet pack do eQuantic.UI.Components:
1
2
3
4
<!-- eQuantic.UI.Components.csproj -->
<ItemGroup>
<Content Include="**\*.cs" Exclude="obj\**;bin\**" PackagePath="tools\source\" />
</ItemGroup>
O pacote Components embarca os próprios arquivos de código C# para a resolução de tipos do compilador.
2. Compilação (build do consumidor)
Durante o dotnet build, o target CompileEQuanticUI do SDK executa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- Sdk/Sdk.targets -->
<Target Name="CompileEQuanticUI" BeforeTargets="Build">
<PropertyGroup>
<!-- Resolve a partir do pacote Components pela propriedade do NuGet -->
<_StandardComponentsDir Condition="'$(PkgeQuantic_UI_Components)' != ''">
$(PkgeQuantic_UI_Components)/tools/source
</_StandardComponentsDir>
<!-- Alternativa pela árvore de código (só em desenvolvimento) -->
<_StandardComponentsDir Condition="'$(_StandardComponentsDir)' == ''">
$(MSBuildThisFileDirectory)../../eQuantic.UI.Components
</_StandardComponentsDir>
</PropertyGroup>
<Error Text="eQuantic.UI: Standard components not found. Ensure eQuantic.UI.Components package is installed."
Condition="'$(_StandardComponentsDir)' == '' Or !Exists('$(_StandardComponentsDir)')" />
<Exec Command="dotnet $(EqcCliPath) &quot;$(MSBuildProjectDirectory);$(_StandardComponentsDir)&quot; ..." />
</Target>
Principais benefícios da arquitetura
Desacoplamento: o SDK não embarca artefatos de outros pacotes
Versionamento correto: o consumidor pode usar Runtime 0.1.3 + SDK 0.1.2 de forma independente
Sem duplicação: cada artefato existe só no pacote de origem dele
Flexibilidade: os pacotes evoluem independentemente, sem acoplamento forte
Interface clara: o SDK referencia os pacotes por propriedades bem definidas do NuGet ($(Pkg*))
Alternativa em desenvolvimento: os caminhos da árvore de código funcionam para o desenvolvimento do framework
Estratégia de bundle único do runtime
O Runtime usa a configuração inlineDynamicImports: true do Vite para criar um bundle único:
1
2
3
4
5
6
7
8
9
10
// eQuantic.UI.Runtime/vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
inlineDynamicImports: true, // ← Cria um bundle único
},
},
},
});
Por que um bundle único?
Entrega simplificada: só um arquivo para copiar (runtime.js)
Sem gestão de chunks: evita problemas com chunks separados de logger-_.js, error-overlay-_.js
Distribuição confiável: garante que todos os recursos do runtime (logger, overlay de erro) estejam incluídos
Tamanho pequeno: ~49KB minificado com todos os recursos incluídos
Sem isso, o Vite criaria chunks separados para os imports dinâmicos, e o SDK precisaria copiar vários arquivos com nomes baseados em hash.