eQuantic.UI Build FlowThis 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────────┘
┌──────────────────────────────────┐
│ ResolveBunForServer target: │
│ │ Runtime.Osx64/tools/bun/ │
│ │ Runtime.Win64/tools/bun/ │
│ │ Runtime.Linux64/tools/bun/ │
│ ├─ Extracts from .zip if needed │
│ BundleRuntime target: │
│ └─ "$(_BunPath)" build boot.ts │
└──────────────────────────────────┘
wwwroot/runtime.js (embedded in Server.dll)
┌──────────────────────────────────┐
└──────────────────────────────────┘
artifacts/packages/eQuantic.UI.Server.0.1.1.nupkg
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONSUMER (client project) │
└─────────────────────────────────────────────────────────────────────────────┘
├─ Sdk="eQuantic.UI.Sdk/0.1.1"
└─ PackageReference: eQuantic.UI.Server, eQuantic.UI.Runtime
┌──────────────────────────────────┐
│ NuGet installs packages: │
│ ├─ eQuantic.UI.Server │
│ ├─ eQuantic.UI.Runtime │
│ └─ 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
> 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.
┌──────────────────────────────────┐
│ SDK.targets executes: │
│ └─ $(PkgeQuantic_UI_Runtime_ │
│ Osx64)/tools/bun/*.zip │
│ 2. EnsureBunExtracted │
│ └─ Defines $(BunPath) │
│ 4. InstallBunPackages │
│ ├─ <BunPackage> → bun add │
│ └─ Symlink node_modules │
│ └─ dotnet eqc.dll ... --bun │
│ 6. CopyEQuanticRuntime │ ◄── Runtime.js deployment
│ └─ Copy from Runtime package │
│ to wwwroot/_equantic/ │
└──────────────────────────────────┘
├─ runtime.js (from Runtime package)
└─ *.js (compiled components)
┌──────────────────────────────────┐
│ ├─ runtime.js (from Server.dll) │
│ └─ *.js (from wwwroot/_equantic)│
└──────────────────────────────────┘
Browser loads application
Server (package build)
eQuantic.UI.Runtime.{OS}/tools/bun/ (source tree)
SDK (consumer)
$(PkgeQuantic_UI_Runtime_{OS})/tools/bun/ (NuGet cache)
The consumer only needs:
•
dotnet restore + dotnet build
No Node.js, npm, or global Bun installation required.
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)
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/
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.
Before (Problematic):
├─ Embedded runtime.js (copied from Runtime)
└─ Embedded *.cs files (copied from Components)
Problems: tight coupling, version conflicts, artifact duplication
After (Correct):
└─ tools/runtime/runtime.js (self-contained)
└─ tools/source/*.cs (self-contained)
└─ 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 -->
<Content Include="dist\index.js" PackagePath="tools\runtime\runtime.js"
Condition="Exists('dist\index.js')" />
The Runtime package embeds its own compiled JavaScript artifact.
2. Deployment (Consumer Build)
During dotnet build, the SDK's CopyEQuanticRuntime target executes:
<Target Name="CopyEQuanticRuntime" AfterTargets="CompileEQuanticUI"
Condition="'$(EnableEQuanticUICompilation)' == 'true'">
<!-- Resolve from Runtime package via NuGet property -->
<_RuntimeSourcePath Condition="'$(PkgeQuantic_UI_Runtime)' != ''">
$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js
<!-- Fallback to source tree (development only) -->
<_RuntimeSourcePath Condition="'$(_RuntimeSourcePath)' == ''">
$(MSBuildThisFileDirectory)../../eQuantic.UI.Runtime/dist/index.js
<_RuntimeDestPath>$(MSBuildProjectDirectory)/$(EQuanticOutputPath)runtime.js</_RuntimeDestPath>
<Error Text="eQuantic.UI: Runtime not found. Ensure eQuantic.UI.Runtime package is installed."
Condition="'$(_RuntimeSourcePath)' == '' Or !Exists('$(_RuntimeSourcePath)')" />
<Copy SourceFiles="$(_RuntimeSourcePath)" DestinationFiles="$(_RuntimeDestPath)" />
Components Source Deployment
1. Packaging (Development)
During dotnet pack of eQuantic.UI.Components:
<!-- eQuantic.UI.Components.csproj -->
<Content Include="**\*.cs" Exclude="obj\**;bin\**" PackagePath="tools\source\" />
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:
<Target Name="CompileEQuanticUI" BeforeTargets="Build">
<!-- 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>
<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)" ..." />
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:
// eQuantic.UI.Runtime/vite.config.ts
export default defineConfig({
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.