eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilation
eQuantic.UI Build Flow
Edit this page
2 min read
🌐 This page in: English · Português
This document describes the eQuantic.UI build flow, demonstrating how the framework maintains zero external dependencies for the consumer.
Visual Flow
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
┌─────────────────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT (source tree) │
└─────────────────────────────────────────────────────────────────────────────┘
reconciler.ts, component.ts, etc.
┌──────────────────────────────────┐
│ npm run build │ (only during development)
│ (eQuantic.UI.Runtime) │
└──────────────────────────────────┘
dist/index.js (compiled runtime)
boot.ts ──────imports────────┘
┌──────────────────────────────────┐
│ dotnet build │
│ (eQuantic.UI.Server) │
│ │
│ ResolveBunForServer target: │
│ ├─ Looks for Bun in: │
│ │ Runtime.Osx64/tools/bun/ │
│ │ Runtime.Win64/tools/bun/ │
│ │ Runtime.Linux64/tools/bun/ │
│ ├─ Extracts from .zip if needed │
│ └─ chmod +x (Unix) │
│ │
│ BundleRuntime target: │
│ └─ "$(_BunPath)" build boot.ts │
└──────────────────────────────────┘
wwwroot/runtime.js (embedded in Server.dll)
┌──────────────────────────────────┐
│ dotnet pack │
│ (eQuantic.UI.Server) │
└──────────────────────────────────┘
artifacts/packages/eQuantic.UI.Server.0.1.1.nupkg
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONSUMER (client project) │
└─────────────────────────────────────────────────────────────────────────────┘
MyApp.csproj
├─ Sdk="eQuantic.UI.Sdk/0.1.1"
└─ PackageReference: eQuantic.UI.Server, eQuantic.UI.Runtime
┌──────────────────────────────────┐
│ dotnet restore │
│ │
│ NuGet installs packages: │
│ ├─ eQuantic.UI.Sdk │
│ ├─ eQuantic.UI.Server │
│ ├─ eQuantic.UI.Runtime │
│ │ └─ (meta-package) │
│ └─ eQuantic.UI.Runtime.Osx64 │ ◄── Bun embedded here!
│ └─ tools/bun/bun-darwin.zip │
└──────────────────────────────────┘
> **Which Bun?** `src/eQuantic.UI.Runtime/bun-toolchain.json` holds the version and a SHA-256 per
> platform, checked on every test run. The digests prove the committed bytes are the ones the
> release published; the version is verified by extracting the host's binary and running it, because
> a manifest nobody compares against the thing it describes drifts from it.
>
> All six platform packages (`Osx64`, `OsxArm64`, `Win64`, `WinArm64`, `Linux64`, `LinuxArm64`) carry
> the same build; a different Bun per architecture is how you get a bundle that only fails on one
> person's machine.
>
> Updating it: replace the `.zip` files, run the tests once with `EQ_UPDATE_BUN_MANIFEST=1`, and read
> the diff. A version that moved with digests that did not is a mistake, and so is the reverse.
┌──────────────────────────────────┐
│ dotnet build │
│ │
│ SDK.targets executes: │
│ │
│ 1. ResolveBunZipPath │
│ └─ $(PkgeQuantic_UI_Runtime_ │
│ Osx64)/tools/bun/*.zip │
│ │
│ 2. EnsureBunExtracted │
│ ├─ Unzip if needed │
│ └─ chmod +x (Unix) │
│ │
│ 3. ResolveBunPath │
│ └─ Defines $(BunPath) │
│ │
│ 4. InstallBunPackages │
│ ├─ <BunPackage> → bun add │
│ └─ Symlink node_modules │
│ │
│ 5. CompileEQuanticUI │
│ └─ dotnet eqc.dll ... --bun │
│ "$(BunPath)" │
│ │
│ 6. CopyEQuanticRuntime │ ◄── Runtime.js deployment
│ └─ Copy from Runtime package │
│ to wwwroot/_equantic/ │
│ │
│ └─ "$(BunPath)" x │
└──────────────────────────────────┘
wwwroot/_equantic/
├─ runtime.js (from Runtime package)
└─ *.js (compiled components)
┌──────────────────────────────────┐
│ dotnet run │
│ │
│ Server serves: │
│ ├─ runtime.js (from Server.dll) │
│ └─ *.js (from wwwroot/_equantic)│
└──────────────────────────────────┘
Browser loads application
Bun Source by Component
Component
Bun Source
Server (package build)
eQuantic.UI.Runtime.{OS}/tools/bun/ (source tree)
SDK (consumer)
$(PkgeQuantic_UI_Runtime_{OS})/tools/bun/ (NuGet cache)
Consumer Requirements
The consumer only needs:
.NET SDK 10.0
dotnet restore + dotnet build
No Node.js, npm, or global Bun installation required.
Key Files
File
Responsibility
Sdk/Sdk.targets
Resolves Bun, installs <BunPackage> items, compiles components
Server.csproj
Resolves Bun from source tree, bundles runtime.js
Runtime.{OS}.csproj
Packages Bun executable for each platform
MSBuild Targets (Execution Order)
In SDK (consumer)
1.
ResolveBunZipPath - Finds the Bun .zip in NuGet cache
2.
EnsureBunExtracted - Extracts the executable if needed
3.
ResolveBunPath - Defines $(BunPath) for later use
4.
InstallBunPackages - Installs <BunPackage> items via bun add (see BunPackage)
5.
CompileEQuanticUI - Transpiles C# → TypeScript → JavaScript
6.
CopyEQuanticRuntime - Copies runtime.js from Runtime package to wwwroot/\_equantic/
In Server (development)
1.
ResolveBunForServer - Finds Bun in source tree
2.
BundleRuntime - Compiles boot.ts → runtime.js
Package Architecture & Self-Containment
eQuantic.UI follows a self-contained package architecture where each package manages its own artifacts. The SDK acts as an orchestrator, referencing other packages via NuGet's $(Pkg*) properties.
Architecture Principles
Before (Problematic):
1
2
3
SDK Package ❌
├─ Embedded runtime.js (copied from Runtime)
└─ Embedded *.cs files (copied from Components)
Problems: tight coupling, version conflicts, artifact duplication
After (Correct):
1
2
3
4
5
6
7
8
Runtime Package ✅
└─ tools/runtime/runtime.js (self-contained)
Components Package ✅
└─ tools/source/*.cs (self-contained)
SDK Package ✅
└─ References other packages via $(PkgeQuantic_UI_*)
Benefits: decoupling, correct versioning, no duplication
Runtime.js Deployment
1. Packaging (Development)
During dotnet pack of 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>
The Runtime package embeds its own compiled JavaScript artifact.
2. Deployment (Consumer Build)
During dotnet build, the SDK's CopyEQuanticRuntime target executes:
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 from Runtime package via NuGet property -->
<_RuntimeSourcePath Condition="'$(PkgeQuantic_UI_Runtime)' != ''">
$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js
</_RuntimeSourcePath>
<!-- Fallback to source tree (development only) -->
<_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>
Components Source Deployment
1. Packaging (Development)
During dotnet pack of eQuantic.UI.Components:
1
2
3
4
<!-- eQuantic.UI.Components.csproj -->
<ItemGroup>
<Content Include="**\*.cs" Exclude="obj\**;bin\**" PackagePath="tools\source\" />
</ItemGroup>
The Components package embeds its own C# source files for compiler type resolution.
2. Compilation (Consumer Build)
During dotnet build, the SDK's CompileEQuanticUI target executes:
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 from Components package via NuGet property -->
<_StandardComponentsDir Condition="'$(PkgeQuantic_UI_Components)' != ''">
$(PkgeQuantic_UI_Components)/tools/source
</_StandardComponentsDir>
<!-- Fallback to source tree (development only) -->
<_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>
Key Architectural Benefits
Decoupling: SDK doesn't embed artifacts from other packages
Correct Versioning: Consumer can use Runtime 0.1.3 + SDK 0.1.2 independently
No Duplication: Each artifact exists only in its source package
Flexibility: Packages evolve independently without tight coupling
Clear Interface: SDK references packages via well-defined NuGet properties ($(Pkg*))
Development Fallback: Source tree paths work for framework development
Runtime Single Bundle Strategy
The Runtime uses Vite's inlineDynamicImports: true configuration to create a single bundle:
1
2
3
4
5
6
7
8
9
10
// eQuantic.UI.Runtime/vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
inlineDynamicImports: true, // ← Creates single bundle
},
},
},
});
Why Single Bundle?
Simplified Deployment: Only one file to copy (runtime.js)
No Chunk Management: Avoids issues with separate logger-_.js, error-overlay-_.js chunks
Reliable Distribution: Guaranteed that all runtime features (logger, error overlay) are included
Small Size: ~49KB minified with all features included
Without this, Vite would create separate chunks for dynamic imports, and the SDK would need to copy multiple files with hash-based names.