-
Notifications
You must be signed in to change notification settings - Fork 1
BuildFlow
This document describes the eQuantic.UI build flow, demonstrating how the framework maintains zero external dependencies for the consumer.
┌─────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 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. CompileEQuanticUI │
│ └─ dotnet eqc.dll ... --bun │
│ "$(BunPath)" │
│ │
│ 5. CopyEQuanticRuntime │ ◄── Runtime.js deployment
│ └─ Copy from Runtime package │
│ to wwwroot/_equantic/ │
│ │
│ 6. BuildCSS (Tailwind) │
│ └─ "$(BunPath)" x │
│ @tailwindcss/cli ... │
└──────────────────────────────────┘
│
▼
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
| Component | Bun Source |
|---|---|
| Server (package build) |
eQuantic.UI.Runtime.{OS}/tools/bun/ (source tree) |
| SDK (consumer) |
$(PkgeQuantic_UI_Runtime_{OS})/tools/bun/ (NuGet cache) |
| Tailwind | Uses $(BunPath) resolved by SDK |
The consumer only needs:
- .NET SDK 8.0
-
dotnet restore+dotnet build
No Node.js, npm, or global Bun installation required.
| File | Responsibility |
|---|---|
Sdk/Sdk.targets |
Resolves Bun from NuGet cache, compiles components |
Server.csproj |
Resolves Bun from source tree, bundles runtime.js |
Tailwind.targets |
Uses $(BunPath) to generate CSS |
Runtime.{OS}.csproj |
Packages Bun executable for each platform |
- ResolveBunZipPath - Finds the Bun .zip in NuGet cache
- EnsureBunExtracted - Extracts the executable if needed
-
ResolveBunPath - Defines
$(BunPath)for later use - CompileEQuanticUI - Transpiles C# → TypeScript → JavaScript
- CopyEQuanticRuntime - Copies runtime.js from Runtime package to wwwroot/_equantic/
- BuildCSS (Tailwind) - Generates CSS with Tailwind CLI
- ResolveBunForServer - Finds Bun in source tree
- BundleRuntime - Compiles boot.ts → runtime.js
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.
Before (Problematic):
SDK Package ❌
├─ Embedded runtime.js (copied from Runtime)
└─ Embedded *.cs files (copied from Components)
Problems: tight coupling, version conflicts, artifact duplication
After (Correct):
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
1. Packaging (Development)
During dotnet pack of eQuantic.UI.Runtime:
<!-- 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:
<!-- 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>1. Packaging (Development)
During dotnet pack of eQuantic.UI.Components:
<!-- 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:
<!-- 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) "$(MSBuildProjectDirectory);$(_StandardComponentsDir)" ..." />
</Target>- 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
The Runtime uses Vite's inlineDynamicImports: true configuration to create a single bundle:
// 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.