Package ArchitectureThis document explains eQuantic.UI's package architecture and the principles behind its self-contained design.
Core Principle: Self-Contained Packages
Each eQuantic.UI package is self-contained and manages its own artifacts. The SDK acts as an orchestrator that references other packages via NuGet's auto-generated properties.
Anti-pattern the design rejects, the SDK embedding other packages' artifacts:
├─ tools/runtime/runtime.js (copied from Runtime)
└─ tools/StandardComponents/*.cs (copied from Components)
Why that shape fails:
•
Tight Coupling: SDK has direct knowledge of Runtime and Components internals
•
Version Conflicts: Consumer installs Runtime 0.1.3 but SDK contains embedded 0.1.2 artifacts
•
Artifact Duplication: Same files exist in multiple packages
•
Inflexible: Can't independently update Runtime or Components without republishing SDK
The Architecture: Self-Contained Packages
eQuantic.UI.Runtime.nupkg ✅
└─ tools/runtime/runtime.js (self-managed)
eQuantic.UI.Components.nupkg ✅
└─ tools/source/*.cs (self-managed)
├─ Sdk/Sdk.props (auto-includes Core, Components, Server, Runtime)
└─ Sdk/Sdk.targets (references packages via $(Pkg*) properties)
Benefits:
•
✅ Decoupling: Each package owns and manages its artifacts
•
✅ Correct Versioning: Consumer's installed version is always used
•
✅ No Duplication: Single source of truth per artifact
•
✅ Independent Evolution: Packages can be updated separately
•
✅ Clear Interface: SDK uses well-defined NuGet properties
Purpose: Core abstractions and types
Contains:
•
HtmlNode, HtmlElement base types
•
Component lifecycle abstractions
Packages: Only compiled DLL (no additional artifacts)
Purpose: Standard component library (Button, Input, Container, etc.)
Contains:
•
Compiled DLL for runtime usage
•
Source files (tools/source/*.cs) for compiler type resolution
Packaging:
<!-- eQuantic.UI.Components.csproj -->
<Content Include="**\*.cs" Exclude="obj\**;bin\**"
PackagePath="tools\source\" />
Why Source Files?
The compiler (eqc.dll) needs access to component source code to resolve external types during compilation. When a consumer uses <Button>, the compiler looks up the Button class definition from the Components package.
SDK Usage:
<!-- Resolved automatically by NuGet -->
<_ComponentsSource>$(PkgeQuantic_UI_Components)/tools/source</_ComponentsSource>
<!-- Passed to compiler -->
<Exec Command="dotnet eqc.dll "$(ProjectDir);$(_ComponentsSource)" ..." />
Purpose: Browser runtime (Virtual DOM, reconciler, state management)
Contains:
•
TypeScript/JavaScript runtime compiled with Vite
•
runtime.js (tools/runtime/runtime.js) - single bundled file (~49KB)
Packaging:
<!-- eQuantic.UI.Runtime.csproj -->
<Content Include="dist\index.js"
PackagePath="tools\runtime\runtime.js"
Condition="Exists('dist\index.js')" />
Build Process:
1.
TypeScript source → npm run build (Vite + tsc)
2.
Output: dist/index.js (single bundle via inlineDynamicImports)
3.
Packaged: eQuantic.UI.Runtime.nupkg/tools/runtime/runtime.js
SDK Usage:
<!-- Resolved automatically by NuGet -->
<_RuntimeSource>$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js</_RuntimeSource>
<!-- Copied to consumer's wwwroot -->
<Copy SourceFiles="$(_RuntimeSource)"
DestinationFiles="wwwroot/_equantic/runtime.js" />
eQuantic.UI.Runtime.{Platform}
Purpose: Platform-specific Bun executables (Osx64, Win64, Linux64)
Contains:
•
Bun executable (zipped) for bundling
•
Platform-specific binaries (~60MB each)
Why Separate Packages?
•
Consumers only download the executable for their platform
•
Reduces package size (no need for all 3 platforms)
•
Clean separation of runtime logic vs. build tools
Purpose: ASP.NET Core integration
Contains:
•
Server-side rendering (SSR)
•
Server Actions RPC system
•
Embedded runtime.js served at /_equantic/runtime.js
Note: Server embeds its own copy of runtime.js as an EmbeddedResource for serving via HTTP. This is separate from the consumer's build-time copy.
Purpose: MSBuild SDK orchestrator
Contains:
•
Sdk.props - Auto-includes Core, Components, Server, Runtime packages
•
Sdk.targets - MSBuild targets for compilation, bundling, CSS generation
•
tools/net10.0/eqc.dll - The compiler executable
Does NOT Contain:
•
❌ Runtime artifacts (references Runtime package)
•
❌ Components source (references Components package)
Responsibilities:
1.
Package Management: Auto-includes required packages via Sdk.props
2.
Build Orchestration: Coordinates compilation, bundling, CSS generation
3.
Artifact Resolution: Resolves runtime.js and component sources from their packages
4.
Tooling: Provides compiler (eqc.dll) and build infrastructure
eQuantic.UI.Lucide / Heroicons / ...
Purpose: Icon set providers
Contains:
•
Specialized icon components
•
IIconProvider implementation
•
Fluent API: Registers itself via Use{Name}Icons() extension on UIOptions.
eQuantic.UI.Charts.ChartJs / ApexCharts
Purpose: Specialized chart components
Contains:
•
Asset declarations via IRequireAssets
•
Fluent API: Registers itself via UseChartJs() / UseApexCharts() extensions on UIOptions.
NuGet automatically generates $(Pkg*) properties for installed packages:
<!-- Auto-generated in obj/*.nuget.g.props -->
<PkgeQuantic_UI_Core>/Users/name/.nuget/packages/equantic.ui.core/0.1.2</PkgeQuantic_UI_Core>
<PkgeQuantic_UI_Components>/Users/name/.nuget/packages/equantic.ui.components/0.1.2</PkgeQuantic_UI_Components>
<PkgeQuantic_UI_Runtime>/Users/name/.nuget/packages/equantic.ui.runtime/0.1.2</PkgeQuantic_UI_Runtime>
<PkgeQuantic_UI_Runtime_Osx64>/Users/name/.nuget/packages/equantic.ui.runtime.osx64/0.1.2</PkgeQuantic_UI_Runtime_Osx64>
SDK uses these to resolve artifacts:
<Target Name="CopyEQuanticRuntime">
<_RuntimeSource>$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js</_RuntimeSource>
<Copy SourceFiles="$(_RuntimeSource)" DestinationFiles="..." />
Benefits:
•
✅ SDK doesn't need to know package structure details
•
✅ Works with any package version consumer installs
•
✅ Automatic cache resolution by NuGet
Packages can evolve independently:
<!-- Consumer can mix versions -->
<PackageReference Include="eQuantic.UI.Core" Version="0.1.3" />
<PackageReference Include="eQuantic.UI.Runtime" Version="0.1.4" />
<PackageReference Include="eQuantic.UI.Sdk" Version="0.1.2" />
The SDK will use:
•
Runtime 0.1.4's runtime.js (not 0.1.2's embedded copy)
•
Components 0.1.3's source files (not 0.1.2's embedded copy)
For simplicity, the SDK's Sdk.props defines a default version:
<EQuanticUIVersion>0.1.2</EQuanticUIVersion>
<PackageReference Include="eQuantic.UI.Core" Version="$(EQuanticUIVersion)" />
<PackageReference Include="eQuantic.UI.Components" Version="$(EQuanticUIVersion)" />
<PackageReference Include="eQuantic.UI.Runtime" Version="$(EQuanticUIVersion)" />
<PackageReference Include="eQuantic.UI.Server" Version="$(EQuanticUIVersion)" />
Users can override:
<EQuanticUIVersion>0.1.5</EQuanticUIVersion>
Or manually specify versions:
<PackageReference Include="eQuantic.UI.Runtime" Version="0.1.6" />
Development vs. Consumer Scenarios
Consumer Scenario (NuGet Packages)
↓ NuGet installs packages to cache
~/.nuget/packages/equantic.ui.runtime/0.1.2/
~/.nuget/packages/equantic.ui.components/0.1.2/
↓ NuGet generates $(Pkg*) properties
obj/project.nuget.g.props
Sdk.targets uses $(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js
Development Scenario (Source Tree)
Source tree at: /Users/name/equantic-ui/
↓ Local packages in: artifacts/packages/
↓ NuGet.config prioritizes local
<add key="local" value="../../artifacts/packages" />
↓ Fallback paths in Sdk.targets
<_RuntimeSource Condition="'$(PkgeQuantic_UI_Runtime)' == ''">
$(MSBuildThisFileDirectory)../../eQuantic.UI.Runtime/dist/index.js
Benefits:
•
Framework developers can work directly with source
•
No need to pack/restore during every change
•
Same targets work for both scenarios
1.
Let packages manage their own artifacts
•
Runtime packages runtime.js
•
Components packages source files
2.
Reference via NuGet properties
•
Use $(PkgeQuantic_UI_*) instead of hardcoded paths
3.
Provide development fallbacks
•
Allow SDK to find artifacts in source tree when developing the framework
4.
Include clear error messages
•
Tell users which package is missing when artifacts not found
1.
Don't embed other packages' artifacts
•
SDK should NOT copy Runtime's runtime.js into itself
2.
Don't use relative paths across packages
•
Bad: $(MSBuildThisFileDirectory)../../../Runtime/dist/
•
Good: $(PkgeQuantic_UI_Runtime)/tools/runtime/
3.
Don't assume package versions match
•
Consumer may use Runtime 0.1.3 + SDK 0.1.2
4.
Don't create circular dependencies
•
Packages should have clear dependency graph
Error: eQuantic.UI: Runtime not found. Ensure eQuantic.UI.Runtime package is installed.
Cause: $(PkgeQuantic_UI_Runtime) is empty
Fix:
# Check that Runtime package is installed
ls ~/.nuget/packages/equantic.ui.runtime/0.1.2/
Components source not found
Error: eQuantic.UI: Standard components not found. Ensure eQuantic.UI.Components package is installed.
Cause: $(PkgeQuantic_UI_Components) is empty
Fix:
# Verify Components package has source files
ls ~/.nuget/packages/equantic.ui.components/0.1.2/tools/source/
Symptom: Build uses old runtime.js despite updating Runtime package
Cause: NuGet cache not cleared
Fix:
# Clear specific package from cache
rm -rf ~/.nuget/packages/equantic.ui.runtime/0.1.2
dotnet restore --force --no-cache
•
Runtime - Runtime architecture and distribution •
Compiler - How the C# to JS compiler works