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