-
Notifications
You must be signed in to change notification settings - Fork 1
PackageArchitecture
This document explains eQuantic.UI's package architecture and the principles behind its self-contained design.
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:
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
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
Purpose: Core abstractions and types
Contains:
-
IComponentinterface -
HtmlNode,HtmlElementbase types - Component lifecycle abstractions
- Render context
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 -->
<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 "$(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 -->
<ItemGroup>
<Content Include="dist\index.js"
PackagePath="tools\runtime\runtime.js"
Condition="Exists('dist\index.js')" />
</ItemGroup>Build Process:
- TypeScript source →
npm run build(Vite + tsc) - Output:
dist/index.js(single bundle viainlineDynamicImports) - 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" />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
- 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.
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:
-
Package Management: Auto-includes required packages via
Sdk.props - Build Orchestration: Coordinates compilation, bundling, CSS generation
- Artifact Resolution: Resolves runtime.js and component sources from their packages
-
Tooling: Provides compiler (
eqc.dll) and build infrastructure
Purpose: Icon set providers
Contains:
- SVG resolution logic
- Specialized icon components
-
IIconProviderimplementation -
Fluent API: Registers itself via
Use{Name}Icons()extension onUIOptions.
Purpose: Specialized chart components
Contains:
- Chart wrappers
- Asset declarations via
IRequireAssets -
Fluent API: Registers itself via
UseChartJs()/UseApexCharts()extensions onUIOptions.
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
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:
<!-- 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>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
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
-
Let packages manage their own artifacts
- Runtime packages runtime.js
- Components packages source files
-
Reference via NuGet properties
- Use
$(PkgeQuantic_UI_*)instead of hardcoded paths
- Use
-
Provide development fallbacks
- Allow SDK to find artifacts in source tree when developing the framework
-
Include clear error messages
- Tell users which package is missing when artifacts not found
-
Don't embed other packages' artifacts
- SDK should NOT copy Runtime's runtime.js into itself
-
Don't use relative paths across packages
- Bad:
$(MSBuildThisFileDirectory)../../../Runtime/dist/ - Good:
$(PkgeQuantic_UI_Runtime)/tools/runtime/
- Bad:
-
Don't assume package versions match
- Consumer may use Runtime 0.1.3 + SDK 0.1.2
-
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:
dotnet restore --force
# Check that Runtime package is installed
ls ~/.nuget/packages/equantic.ui.runtime/0.1.2/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/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- Build Flow - Complete build pipeline
- Runtime - Runtime architecture and distribution
- Compiler - How the C# to JS compiler works