k-hole is the game development and design blog of kyle kukshtel
subscribe to my newsletter to get posts delivered directly to your inbox

Building a Web Framework with C# records, Tailwind and HTMX (and no JS)


Goblin-mode-ing C#'s type system and record syntax to render a webpage

February 9, 2024 C# occultism webdev maybe-bad-ideas

webpagez

I’ve been really interested in what the record type (and its accompanying syntax) provides in C# and have been musing a bit on an idea that started with some tooltip explorations a while ago. Specifically something with the self-described pattern of Immutable Prefabs” Mutated With Specific Data.

The idea is that you create a base record type that is some pseudo-generic type, and then provide static versions of that type that define” some base functionality. You then compose those types together in a tree that expects those generic types. The kicker is then that you make that generic type able to render” itself, and then call render from the top of the tree to produce some output type.

So in practice something like

//types.cs
record family(List<relationship> relationships) {
    string Build()  {
        var sb = new StringBuilder();
        foreach(var r in relationships) {
            r.Build(sb);
        }
        return sb.ToString();
    }
}
record relationship(string name) {
    string Build(StringBuilder sb) => name;
}
static family baseFamily(null);
static relationship Dad("dad");
static relationship Mom("mom");

--
//program.cs
Console.WriteLine(
    baseFamily with {
        relationships = [
            Mom,Dad
        ]
    }
);
//output: 
mom
dad

At first blush this seems sort of trivial (and maybe it ultimately is), but the more I think about this pattern the more I feel like there is something here. I’m not sure what yet, but along similar lines I did some experimentation the other day around building a simple website with the same idea.

Here’s the website”:

var endpoint = "http://localhost:8080/"
Site BaseSite = new ([
HTMLBase with { T = [
    Head with { T = [
        Title with { I = "webpagez" },
        Tailwind,
        HTMX
    ]},
    Body with { T = [
        new PollingDiv(endpoint + "poll/")
    ]}
]);

Here’s polling div:

public record PollingDiv(string path) : IBuildable  {
    public void Build(StringBuilder sb)  {
        var rand = new Random();
        var idap = rand.Next();
        var d = 
            Div with {
            Id = "Row"+idap,
            Attrs = [
                HighlightOnHover,
                Get with {value = path},
                Trigger with {value = "every 1s"},
                SwapInner,
                Target with {value = "#polling-container"+idap}
            ],
            T = [
                Div with { 
                    Id = "polling-container"+idap,
                    T = [
                        Div with {
                            Id = "test",
                            Inner = "Waiting..."
                        }
                    ]
                }
            ]
        };
        d.Build(sb);
    }
}

This starts to get at some of the other ideas that kind of underly this experiment. Mainly, that the combination of HTMX and TailwindCSS are starting to approach a modern DOM-attribute-tag-only” reactive-style website…thing. If you don’t need JS to start hooking stuff up and can just spit out Properly Tagged HTML… well that gets kind of interesting.

I’m obviously not the first one to think of this, but I do think wrapping up the same idea into type-safe HTML is maybe kind of interesting? You can build HTML with object-y things and quickly bolt on new functionality without needing to mess with HTML. Should I add in another polling div? Okay look:

var endpoint = "http://localhost:8080/"
Site BaseSite = new ([
HTMLBase with { T = [
    Head with { T = [
        Title with { I = "webpagez" },
        Tailwind,
        HTMX
    ]},
    Body with { T = [
        new PollingDiv(endpoint + "poll/"),
        new PollingDiv(endpoint + "poll/")
    ]}
]);

That was easy, right? Not only did I not have to write this again

<div id="Row631339619" class="hover:outline hover:outline-2 hover:outline-blue-500 p-1" hx-get="http://localhost:8080/poll/" hx-trigger="every 3.828402s" hx-swap="innerHTML" hx-target="#polling-container631339619"><div id="polling-container631339619" class=""><div id="test">Hello, World! 817872580</div></div></div>

But all the specific and injected things into that I could pre-calculate in C# and then apply to the elements before they needed to be rendered.

With this sort of model you can encapsulate common business” components as single instantiatable” types (like the PollingDiv). It’s easy to then hot swap them at a single location and have that propagate to wherever the component is used. Client side feedback is still implemented with CSS via tailwind, so it’s not like you need to do a server call to render a highlight”.

And of course, you can do this whole thing on the server, with basically no client-side code required. You build the server, it builds the initial index.html that gets sent back on the first GET, and then you can send back whatever new components you want when htmx calls back to the server.

All without any real framework” or need for JS, this is just the C# type system.

You can check out the repo where I posted this here but there isn’t really anything special, it’s just C# records:

https://github.com/kkukshtel/webpagez

Would love to know what people think of this, if it’s maybe as dumb as it seems or maybe is actually sort of interesting? Thanks for reading!



Date
February 9, 2024




subscribe to my newsletter to get posts delivered directly to your inbox