Skip to content

PackageArchitecture

Edgar Mesquita edited this page Aug 8, 2026 · 6 revisions

Package Architecture

This 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.

The Design Rationale

Anti-pattern the design rejects — the SDK embedding other packages' artifacts:

eQuantic.UI.Sdk.nupkg ❌
├─ 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)

eQuantic.UI.Sdk.nupkg ✅
├─ 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

Package Responsibilities

eQuantic.UI.Core

Purpose: Core abstractions and types

Contains:

  • IComponent interface
  • HtmlNode, HtmlElement base types
  • Component lifecycle abstractions
  • Render context

Packages: Only compiled DLL (no additional artifacts)

eQuantic.UI.Components

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 -->
<ItemGroup>
  <Content Include="**\*.cs" Exclude="obj\**;bin\**"
           PackagePath="tools\source\" />
</ItemGroup>

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 -->
<PropertyGroup>
  <_ComponentsSource>$(PkgeQuantic_UI_Components)/tools/source</_ComponentsSource>
</PropertyGroup>

<!-- Passed to compiler -->
<Exec Command="dotnet eqc.dll &quot;$(ProjectDir);$(_ComponentsSource)&quot; ..." />

eQuantic.UI.Runtime

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 -->
<ItemGroup>
  <Content Include="dist\index.js"
           PackagePath="tools\runtime\runtime.js"
           Condition="Exists('dist\index.js')" />
</ItemGroup>

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 -->
<PropertyGroup>
  <_RuntimeSource>$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js</_RuntimeSource>
</PropertyGroup>

<!-- 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

eQuantic.UI.Server

Purpose: ASP.NET Core integration

Contains:

  • Server-side rendering (SSR)
  • Server Actions RPC system
  • Middleware and routing
  • 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.

eQuantic.UI.Sdk

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:

  • SVG resolution logic
  • 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:

  • Chart wrappers
  • Asset declarations via IRequireAssets
  • Fluent API: Registers itself via UseChartJs() / UseApexCharts() extensions on UIOptions.

NuGet Package Properties

NuGet automatically generates $(Pkg*) properties for installed packages:

<!-- Auto-generated in obj/*.nuget.g.props -->
<PropertyGroup>
  <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>
</PropertyGroup>

SDK uses these to resolve artifacts:

<!-- Sdk/Sdk.targets -->
<Target Name="CopyEQuanticRuntime">
  <PropertyGroup>
    <_RuntimeSource>$(PkgeQuantic_UI_Runtime)/tools/runtime/runtime.js</_RuntimeSource>
  </PropertyGroup>
  <Copy SourceFiles="$(_RuntimeSource)" DestinationFiles="..." />
</Target>

Benefits:

  • ✅ SDK doesn't need to know package structure details
  • ✅ Works with any package version consumer installs
  • ✅ Automatic cache resolution by NuGet
  • ✅ No hardcoded paths

Version Management

Independent Versioning

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)

Coordinated Versioning

For simplicity, the SDK's Sdk.props defines a default version:

<!-- Sdk/Sdk.props -->
<PropertyGroup>
  <EQuanticUIVersion>0.1.2</EQuanticUIVersion>
</PropertyGroup>

<ItemGroup>
  <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)" />
</ItemGroup>

Users can override:

<PropertyGroup>
  <EQuanticUIVersion>0.1.5</EQuanticUIVersion>
</PropertyGroup>

Or manually specify versions:

<ItemGroup>
  <PackageReference Include="eQuantic.UI.Runtime" Version="0.1.6" />
</ItemGroup>

Development vs. Consumer Scenarios

Consumer Scenario (NuGet Packages)

dotnet restore
  ↓ 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 resolves artifacts
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
<packageSources>
  <add key="local" value="../../artifacts/packages" />
</packageSources>
  ↓ Fallback paths in Sdk.targets
<_RuntimeSource Condition="'$(PkgeQuantic_UI_Runtime)' == ''">
  $(MSBuildThisFileDirectory)../../eQuantic.UI.Runtime/dist/index.js
</_RuntimeSource>

Benefits:

  • Framework developers can work directly with source
  • No need to pack/restore during every change
  • Same targets work for both scenarios

Best Practices

✅ Do

  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

❌ Don't

  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

Troubleshooting

Runtime.js not found

Error: eQuantic.UI: Runtime not found. Ensure eQuantic.UI.Runtime package is installed.

Cause: $(PkgeQuantic_UI_Runtime) is empty

Fix:

dotnet restore --force
# 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:

dotnet restore --force
# Verify Components package has source files
ls ~/.nuget/packages/equantic.ui.components/0.1.2/tools/source/

Using wrong version

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

# Force restore
dotnet restore --force --no-cache

Related Documentation

  • Build Flow - Complete build pipeline
  • Runtime - Runtime architecture and distribution
  • Compiler - How the C# to JS compiler works

Clone this wiki locally