slaveOftime
fsharpblazorFun.Blazor

Summary some use cases of Fun.Blazor

2024-02-08

Used in script

#r "nuget:Microsoft.Extensions.Logging"
#r "nuget:Microsoft.Extensions.DependencyInjection"
#r "nuget:Fun.Blazor"

open Microsoft.Extensions.DependencyInjection
open Fun.Blazor
open Fun.Css

let serviceProvider = ServiceCollection().AddLogging().BuildServiceProvider()

fragment {
    doctype "html5"
    html' {
        lang "en"
        head {
            title { "Fun.Blazor" }
            chartsetUTF8
        }
        body {
            h1 {
                style { color color.green }
                "Cool script"
            }
        }
    }
}
|> html.renderAsString serviceProvider
|> Async.AwaitTask
|> Async.RunSynchronously

It does not only look very clean, but also quite fast, for both html and css generation, here are some benchmarks:

Html benchmark between Falco, Giraffe, Feliz and Fun.Blazor

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
RunGiraffeView 802.3 us 15.92 us 35.28 us 1.00 0.00 166.0156 166.0156 166.0156 2.45 MB 1.00
RunFalcoMarkup 825.1 us 16.39 us 28.70 us 1.03 0.05 166.0156 166.0156 166.0156 2.22 MB 0.91
RunFelizViewEngine 3,524.9 us 44.78 us 41.89 us 4.47 0.24 730.4688 562.5000 164.0625 9.2 MB 3.75
RunFunBlazor 566.8 us 10.52 us 9.32 us 0.72 0.04 179.6875 154.2969 135.7422 1.04 MB 0.42

Css benchmark between Feliz, Fss, and Fun.Css

Method Mean Error StdDev Gen 0 Allocated
BuildStyleWithFunCss 181.2 ns 2.33 ns 2.18 ns 0.0343 432 B
BuildStyleWithFunCssCustom 170.9 ns 2.31 ns 2.05 ns 0.0343 432 B
BuildStyleWithFeliz 519.2 ns 8.90 ns 7.89 ns 0.1593 2,000 B
BuildStyleWithFss 6,042.3 ns 65.63 ns 61.39 ns 0.8545 10,736 B

Another benchmark between Bolero, csharp razor (source generated, should be the fastest) and Fun.Blazor

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
RenderWithRazorCSharp 234.1 ns 3.59 ns 3.36 ns 1.00 0.00 0.0298 376 B 1.00
RenderWithFunBlazorInlineCE 363.5 ns 4.14 ns 3.67 ns 1.55 0.03 0.0443 560 B 1.49
RenderWithFunBlazorArray 499.0 ns 9.82 ns 10.91 ns 2.14 0.05 0.1154 1448 B 3.85
RenderWithBolero 507.9 ns 9.74 ns 11.21 ns 2.17 0.07 0.1173 1480 B 3.94

Benchmark can only be used as reference, because real use cases may become very complex and the business logic will be the main impact.

Used in simple SSR

As it can generate string directly, so there is no one to block you to use it in any fsharp backend frameworks. You can use it in plain asp.net core, or more opionated ones like giraffe or falco etc.

I also made a blog post about it before: https://www.slaveoftime.fun/blog/mix-giraffe,-htmx-and-blazor-together

Something like below:

let uiRoutes: HttpHandler =
    choose [
        // For server side partial views
        subRouteCi "/view" (choose [
            routeCi "/empty" >=> cacheFor30Days >=> View.Build html.none
            routeCi "/products" >=> View.Build Products.Create 
            routeCif "/product-editor/%i" (fun id -> View.Build(ProductEditor.Create id))
            routeCi "/stocks" >=> View.Build (html.customElement<StocksView>(preRender = true))
        ])
        // For pages
        routeCi "/dashboard" >=> View.Build Dashboard.Create
        routeCi "/" >=> View.Build Index.Create
    ]

Here is the repo for it: https://github.com/albertwoo/GiraffeHtmxBlazor/blob/master/UI/Routes.fs

Used in more advanced SSR

Above simple use case, also use htmx for patch DOM when required, but as you can see it requires you define a lot of magic routing string which needed to hook up in htmx attributes which can soon become hard to manage or maitain, no type safety.

Fun.Blazor provides a tempalte to get you started for advanced use cases:

dotnet new fun-ssr -o FunBlazorSSR

It is component (class) style, so the reflection can be used to make stuff automatic for you, like routing, parameters mapping, cache etc.

Below is the simple counter example:

open Fun.Blazor
open Fun.Htmx

type Counter() as this =
    inherit FunComponent()

    let rootId = "counter"

    [<Parameter>]
    member val Count = 0 with get, set

    override _.Render() = div {
        id rootId
        p {
            "Count = "
            this.Count
        }
        button {
            hxTarget ("#" + rootId)
            hxTrigger hxEvt.mouse.click
            hxSwap_outerHTML
            hxGetComponent (QueryBuilder<Counter>().Add((fun x -> x.Count), this.Count + 1))
            "Increase"
        }
    }

It may look a little complex, but it serves many purposes, can scale very easliy and also support dependency injection (you may not like, but it can decouple things and make your code very clean).

Write htmx stuff as component is not enough as advanced, some interative may be very rich, just use htmx is really not enough, or even it can, it will also end up with very complex code. So Fun.Blazor provided supports for streaming components and components powered by websoket.

You can take fun-ssr template as a reference, I also created a project Memory, a picture and video explorer based on time.

Used as SAP (websocket based, fully server side rendering)

This is also same as Microsoft official blazor, all the concepts from it can be applied to Fun.Blazor.

This can be a very productive pattern to use, because you no need to write RESTful APIs for this, everything is on the server side, it can also be used to create very complex components. Also the community is quite big, there are many libraries created for this. You can use Fun.Blazor.Cli to create bindings for those libraries automatically, also Fun.Blazor provides precreated ones like below:

dotnet add package Fun.Blazor.AntDesign --version 0.17.4
dotnet add package Fun.Blazor.ApexCharts --version 2.3.1
dotnet add package Fun.Blazor.BlazorMonaco --version 3.1.0
dotnet add package Fun.Blazor.Microsoft.Authorization --version 8.0.1
dotnet add package Fun.Blazor.Microsoft.FluentUI --version 4.4.0
dotnet add package Fun.Blazor.Microsoft.QuickGrid --version 8.0.1
dotnet add package Fun.Blazor.Microsoft.Web --version 8.0.1
dotnet add package Fun.Blazor.MudBlazor --version 6.15.0

Used as SAP (WASM based, client side rendering)

This is also as powerfull as websocket based ones, but lift all the workloads into users' browser.

The Fun.Blazor Docs is also powered by this pattern.

Used as SAP (auto matically switch between websocket and wasm mode)

This feature is also came from blazor itself, the Fun.Blazor also provide templates for you to get started.

dotnet new fun-blazor -o FunBlazorDemo

Summary

It takes some time to get this far, not easy but fun for me!

Do you like this post?