Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
364df9e
chore: add Renovate configuration
phmatray Feb 18, 2026
dff1b7b
chore: remove Dependabot configuration (switching to Renovate)
phmatray Feb 18, 2026
c007bd0
docs: standardize README following org template (#12)
phmatray Feb 27, 2026
2467b25
chore: Upgrade to .NET 10 (#11)
phmatray Feb 27, 2026
59ac3e2
chore(deps): update dotnet monorepo (#13)
renovate[bot] Feb 27, 2026
c49b261
chore(deps): update dependency anglesharp to 1.4.0 (#14)
renovate[bot] Feb 27, 2026
845625f
chore(deps): update dependency dotnet.reproduciblebuilds to 1.2.39 (#16)
renovate[bot] Feb 27, 2026
522aa3d
chore(deps): update dependency fastendpoints to 5.35.0 (#17)
renovate[bot] Feb 27, 2026
8fadc38
chore(deps): update actions/checkout action to v6 (#18)
renovate[bot] Feb 27, 2026
bf17b7a
chore(deps): update dependency defaultdocumentation to v1 (#19)
renovate[bot] Feb 27, 2026
c36cb1d
chore(deps): update dependency minver to v7 (#21)
renovate[bot] Feb 27, 2026
30825b5
chore(deps): update github artifact actions (#23)
renovate[bot] Feb 27, 2026
e28b32e
chore(deps): update release-drafter/release-drafter action to v6 (#24)
renovate[bot] Feb 27, 2026
59fbdb9
chore(deps): update dependency fastendpoints to v8 (#20)
renovate[bot] Feb 27, 2026
55b642c
chore(deps): update dependency dotnet.reproduciblebuilds to v2 (#25)
renovate[bot] Feb 27, 2026
7cd4f74
chore(deps): update dotnet monorepo to v10 (#22)
renovate[bot] Feb 27, 2026
f3c5e28
fix(ci): update dotnet-version from 8.x to 10.x to match global.json …
phmatray Feb 28, 2026
c2c3e2e
fix: update target framework from net8.0 to net10.0 (#27)
phmatray Feb 28, 2026
299af92
ci: standardize workflow and re-enable tests (#28)
phmatray Feb 28, 2026
62691ca
fix: add build step before NuGet pack in CI workflow (#29)
phmatray Feb 28, 2026
bd5ae45
fix: add missing dotnet restore step in create_nuget job (#30)
phmatray Feb 28, 2026
e693788
fix: resolve build errors and warnings across all source files (#31)
phmatray Feb 28, 2026
25cc365
docs: refresh README for .NET 10 migration (#33)
phmatray Feb 28, 2026
71c9b8f
chore: configure Renovate auto-merge for patch+minor
phmatray Mar 4, 2026
45b4be8
chore: standardize renovate config
phmatray Mar 9, 2026
5f67792
chore: fix renovate config for repo technologies
phmatray Mar 9, 2026
ca1637c
Merge remote-tracking branch 'origin/dev' into chore/standardize-reno…
phmatray Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .github/dependabot.yml

This file was deleted.

75 changes: 43 additions & 32 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,54 @@ defaults:
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# run_test:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout Source Code
# uses: actions/checkout@v3
#
# - name: Setup .NET SDK
# uses: actions/setup-dotnet@v3
# with:
# dotnet-version: 8.x
#
# - name: Run Unit Tests
# run: dotnet test --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
#
# - name: Upload Code Coverage
# uses: codecov/codecov-action@v3
run_test:
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v6

- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.x

- name: Run Unit Tests
run: dotnet test --configuration Release --verbosity normal --collect:"XPlat Code Coverage"

- name: Upload Code Coverage
uses: codecov/codecov-action@v5

create_nuget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer

# Install the .NET SDK indicated in the global.json file
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

- name: Restore dependencies
run: dotnet restore

# Build the project first so that staticwebassets.build.json manifest is generated
- name: Build
run: dotnet build --configuration Release --no-restore

# Create the NuGet package in the folder from the environment variable NuGetDirectory
- run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }}
- name: Create NuGet package
run: dotnet pack --configuration Release --no-build --output ${{ env.NuGetDirectory }}

# Publish the NuGet package as an artifact, so they can be used in the following jobs
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v7
with:
name: nuget
if-no-files-found: error
Expand All @@ -77,12 +89,12 @@ jobs:
steps:
# Install the .NET SDK indicated in the global.json file
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

# Download the NuGet package created in the previous job
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v8
with:
name: nuget
path: ${{ env.NuGetDirectory }}
Expand All @@ -104,7 +116,7 @@ jobs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand All @@ -113,26 +125,25 @@ jobs:
# https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
# You can update this logic if you want to manage releases differently
runs-on: ubuntu-latest
needs: [ validate_nuget, update_release_draft ]
# needs: [ validate_nuget, run_test, update_release_draft ]
needs: [ validate_nuget, run_test, update_release_draft ]
steps:
# Download the NuGet package created in the previous job
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v8
with:
name: nuget
path: ${{ env.NuGetDirectory }}

# Install the .NET SDK indicated in the global.json file
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

# Publish all NuGet packages to NuGet.org
# Use --skip-duplicate to prevent errors if a package with the same version already exists.
# If you retry a failed workflow, already published packages will be skipped without error.
- name: Publish NuGet package
run: |
foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push $file --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
}
240 changes: 239 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,241 @@
# FastComponents

Code web, think .NET 8 with FastComponents for a successful MRA (Multiple Resources Application).
> **Server-side Blazor components rendered as HTMX-powered HTML fragments -- build interactive web UIs with .NET 10 and zero client-side Blazor runtime.**

<!-- Badges: Row 1 — Identity -->
[![Atypical-Consulting - FastComponents](https://img.shields.io/static/v1?label=Atypical-Consulting&message=FastComponents&color=blue&logo=github)](https://github.com/Atypical-Consulting/FastComponents)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
[![.NET 10](https://img.shields.io/badge/.NET-10.0-purple?logo=dotnet)](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)
[![stars - FastComponents](https://img.shields.io/github/stars/Atypical-Consulting/FastComponents?style=social)](https://github.com/Atypical-Consulting/FastComponents)
[![forks - FastComponents](https://img.shields.io/github/forks/Atypical-Consulting/FastComponents?style=social)](https://github.com/Atypical-Consulting/FastComponents)

<!-- Badges: Row 2 — Activity -->
[![GitHub tag](https://img.shields.io/github/tag/Atypical-Consulting/FastComponents?include_prereleases=&sort=semver&color=blue)](https://github.com/Atypical-Consulting/FastComponents/releases/)
[![issues - FastComponents](https://img.shields.io/github/issues/Atypical-Consulting/FastComponents)](https://github.com/Atypical-Consulting/FastComponents/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/Atypical-Consulting/FastComponents)](https://github.com/Atypical-Consulting/FastComponents/pulls)
[![GitHub last commit](https://img.shields.io/github/last-commit/Atypical-Consulting/FastComponents)](https://github.com/Atypical-Consulting/FastComponents/commits/main)

<!-- Badges: Row 3 — Quality -->
[![Build](https://github.com/Atypical-Consulting/FastComponents/actions/workflows/main.yml/badge.svg)](https://github.com/Atypical-Consulting/FastComponents/actions/workflows/main.yml)

<!-- Badges: Row 4 — Distribution -->
[![NuGet](https://img.shields.io/nuget/v/FastComponents.svg)](https://www.nuget.org/packages/FastComponents)

---

## Table of Contents

- [The Problem](#the-problem)
- [The Solution](#the-solution)
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Getting Started](#getting-started)
- [Usage](#usage)
- [Architecture](#architecture)
- [Project Structure](#project-structure)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)

## The Problem

Building interactive web UIs with .NET typically means choosing between the full Blazor Server runtime (with WebSocket overhead and connection state) or Blazor WASM (with large download sizes). If you just want lightweight, server-rendered HTML fragments that respond to user interactions -- the HTMX model -- there is no first-class Blazor integration. You end up writing raw HTML strings or abandoning the Razor component model entirely.

## The Solution

**FastComponents** bridges Blazor's server-side Razor component model with HTMX. You author components using familiar `.razor` syntax with full C# support, and FastComponents renders them as plain HTML fragments served via FastEndpoints. HTMX on the client handles partial page updates -- no WebSocket connections, no WASM downloads, just HTTP requests and HTML responses.

```csharp
// Define a component with HTMX attributes using the HtmxTag helper
<HtmxTag
As="button"
HxGet="/api/counter?Count=1"
HxSwap="outerHTML"
HxTarget="#counter">
Increment
</HtmxTag>
```

## Features

- [x] `HtmxComponentBase` -- base class with all htmx attributes as Blazor `[Parameter]` properties
- [x] `HtmxTag` -- generic Razor component that renders any HTML element with htmx attributes
- [x] `HtmxComponentEndpoint<TComponent>` -- serve components as HTML via FastEndpoints routes
- [x] `HtmxComponentEndpoint<TComponent, TParameters>` -- typed parameter binding from query strings
- [x] `HtmxComponentParameters` -- immutable record base with automatic query string serialization
- [x] `ComponentHtmlResponseService` -- render any Blazor component to an HTML string on the server
- [x] `ClassNamesBuilder` -- fluent, conditional CSS class builder (similar to `classnames` in JS)
- [x] `Hx.Swap` constants and `Hx.TargetId()` helper for type-safe htmx attribute values
- [x] Bundled `htmx.min.js` as a static web asset
- [x] NuGet package with auto-generated API documentation
- [ ] HTML beautifier for formatted output *(stub implemented)*
- [ ] AOT compilation support *(planned)*
- [ ] Project template for `dotnet new` *(planned)*

## Tech Stack

| Layer | Technology |
|-------|-----------|
| Runtime | .NET 10.0 / C# 14 |
| Component model | Blazor SSR (`Microsoft.AspNetCore.Components.Web` 10.0) |
| Endpoint routing | [FastEndpoints](https://fast-endpoints.com/) 8.0 |
| HTML parsing | [AngleSharp](https://anglesharp.github.io/) 1.4 |
| Client interactivity | [htmx](https://htmx.org/) (bundled) |
| Versioning | [MinVer](https://github.com/adamralph/minver) (git-tag based) |
| CI/CD | GitHub Actions (build, NuGet pack, validate, publish) |

## Getting Started

### Prerequisites

- [.NET SDK](https://dotnet.microsoft.com/download) >= 10.0

### Installation

**Option 1 -- NuGet** *(recommended)*

```bash
dotnet add package FastComponents
```

**Option 2 -- From Source**

```bash
git clone https://github.com/Atypical-Consulting/FastComponents.git
cd FastComponents
dotnet build
```

### Setup

Register FastComponents in your `Program.cs`:

```csharp
var builder = WebApplication.CreateBuilder(args);

// Add FastComponents services (registers FastEndpoints + HTML renderer)
builder.Services.AddFastComponents();

var app = builder.Build();

app.UseStaticFiles();

// Map component endpoints
app.UseFastComponents();

app.Run();
```

## Usage

### Define a component

Create a Razor component that inherits from `HtmxComponentBase`:

```razor
@* Counter.razor *@
@inherits HtmxComponentBase<CounterEndpoint.CounterParameters>

<section id="block-counter">
<HtmxTag
As="button"
HxGet="@Parameters.Increment()"
HxSwap="@Hx.Swap.OuterHtml"
HxTarget="@Hx.TargetId("block-counter")">
Increment
</HtmxTag>

<span>Count: @Parameters.Count</span>
</section>
```

### Wire it to an endpoint

```csharp
// Counter.razor.cs
public class CounterEndpoint
: HtmxComponentEndpoint<Counter, CounterEndpoint.CounterParameters>
{
public override void Configure()
{
Get("/ui/blocks/counter");
AllowAnonymous();
}

public record CounterParameters : HtmxComponentParameters
{
public int Count { get; init; } = 0;

public string Increment()
{
var next = this with { Count = Count + 1 };
return next.ToComponentUrl("/ui/blocks/counter");
}
}
}
```

When the button is clicked, htmx sends a GET request to `/ui/blocks/counter?Count=1`, FastComponents renders the Blazor component server-side, and the HTML fragment replaces the target element -- no JavaScript framework required.

## Architecture

```
Browser (htmx) ASP.NET Server
+-----------------+ +-----------------------------------+
| HTML + htmx.js | -- HTTP GET -> | FastEndpoints route |
| | | -> HtmxComponentEndpoint |
| | | -> ComponentHtmlResponseService|
| | | -> HtmlRenderer (Blazor SSR)|
| <-- HTML frag -- | | <- Rendered HTML fragment |
+-----------------+ +-----------------------------------+
```

### Project Structure

```
FastComponents/
├── src/
│ └── FastComponents/ # Core library (NuGet package)
│ ├── Components/
│ │ ├── Base/ # HtmxComponentBase, ClassNamesBuilder, interfaces
│ │ └── HtmxTag/ # Generic htmx-aware Razor component
│ ├── Endpoints/ # HtmxComponentEndpoint base classes
│ ├── Services/ # ComponentHtmlResponseService, HtmlBeautifier
│ ├── Utilities/ # Hx helper (swap constants, target helpers)
│ └── wwwroot/ # Bundled htmx.min.js
├── demo/
│ └── HtmxAppServer/ # Demo app (Counter, MovieCharacters examples)
├── docs/ # Auto-generated API documentation
├── build/ # Build scripts (versioning)
└── .github/workflows/ # CI pipeline (build, pack, validate, deploy)
```

## Roadmap

- [ ] Implement HTML beautifier for formatted debug output
- [ ] Add AOT compilation support
- [ ] Publish a `dotnet new` project template
- [ ] Add unit and integration tests
- [ ] Expand component library with common UI patterns

> Want to contribute? Pick any roadmap item and open a PR!

## Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit using [conventional commits](https://www.conventionalcommits.org/) (`git commit -m 'feat: add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

[Apache-2.0](LICENSE) (c) 2020-2026 [Atypical Consulting](https://atypical.garry-ai.cloud)

---

Built with care by [Atypical Consulting](https://atypical.garry-ai.cloud) -- opinionated, production-grade open source.

[![Contributors](https://contrib.rocks/image?repo=Atypical-Consulting/FastComponents)](https://github.com/Atypical-Consulting/FastComponents/graphs/contributors)
Loading