Fun Blazor
.NET WASM is still loading. You can interact in this page after it's fully loaded.

Current page is prerendered.

About

image

This is a project aimed at making it easier for F# developers to write Blazor applications.

Features include:

  1. Allows F# for Blazor development
  2. Use computation expression (CE) style DSL for internal and third-party Blazor libraries
  3. Use dependency injection (html.inject/html.comp)
  4. Leverages the Adaptive model (adaptiview/AdaptiveForm) (highly recommended), or the elmish model (html.elmish)
  5. Implements Giraffe-style routing (html.route)
  6. Provides type-safe stylesheet creation using Fun.Css
  7. Converts HTML to CE style with Fun.Dev.Tools

Benchmarks

Method Mean Error StdDev Gen 0 Allocated
RenderWithRazorCSharp 400.3 ns 6.99 ns 6.20 ns 0.0610 384 B
RenderWithBolero 926.1 ns 17.49 ns 17.96 ns 0.2546 1,600 B
RenderWithFunBlazorCE 731.1 ns 14.07 ns 21.49 ns 0.1173 736 B
RenderWithFunBlazorTemplate 2,569.9 ns 42.22 ns 39.50 ns 0.6752 4,240 B

Simple demo

Here is a basic counter that uses an adaptive model:

Count1=1

Another demo that uses html.inject:

Here is the count 0

How does this work?

Fun.Blazor provides a series of delegates for Blazor to handle. For example, when you write:

let demo =
    div {
        class' "cool"
    }

This code essentially becomes:

let demo =
    NodeRenderFragment(fun comp builder index ->  // delegate
        builder.OpenElement(index, "div")
        bulder.AddAttribute(index + 1, "class", "cool")
        builer.CloseElement()
        index + 2
    )

type NodeRenderFragment = delegate of root: IComponent * builder: RenderTreeBuilder * sequence: int -> int

In essence, you have created a delegate that will be passed to a component, which will manage the rendering or building of the DOM tree. This approach is similar to what the Razor engine would generate in the C# world.

Components can be created using adaptiview, html.inject, etc. These components are normal Blazor components that inherit from ComponentBase.

Considerations before using Fun.Blazor:

There are a few things to keep in mind:

  1. The F# compiler has performance issues with intellisense for some large computation expressions (CEs). It is better to keep single CE blocks and files small, or use sequences like seq, list, or array with childContent for better intellisense:

    div {
        attributes ...
        childContent [ // ✅ recommended for more than one child item
            div { "hi" }
            ...a lot of child items
        ]
    }
    

    instead of:

    div {
        attributes ...
        div { "hi" }
        ...a lot of child items  ❌
    }
    
  2. Hot-reload

    The default templates provide limited hot-reload support. Too many files can slow down the hot-reload process, so for best results, add // hot-reload at the top of files you want to enable hot-reload for. For more information, see the Hot-reload in Fun.Blazor blog post or document.

  3. Attribute, items position in CE

    When using a ref attribute, you should place it like so:

    div {
        attributes ...
        ref (fun x -> ()) // ✅
        childContent [ ... ]
    }
    

    Or:

    div {
        attributes ...
        ref (fun x -> ()) // ✅
        div { 1 }
        div { 1 }
        // ...
    }