Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

My journey with Feliz | A comparison between Fable.React and Feliz #155

Open
MangelMaxime opened this issue Apr 7, 2020 · 25 comments
Open

Comments

@MangelMaxime
Copy link
Contributor

Hello,

this issue is for sharing my experience with Feliz and Feliz.Bulma.

This feedback is based on converting Fable Repl from Fable.React+Fulma to Feliz+Feliz.Bulma.

In the first section, I will make a listing of all the things I liked or had problems with when using Feliz. I will do something similar for Fable.React and Fulma because they are not exempt from good and less good part.

Then, in a second time, I will try to explain how went my experience with Feliz and Feliz.Bulma. The goal is to share with you how my point of view changed over time and why.

Important

I know that the subject I am analysing is sensitive and that something that afraid me. But please, remember to keep the comment section positive.

Table of content

Categorization system

I tried to find a way to organise my feedbacks, the classic "pros vs cons" felt too limited and aggressive to me.

Instead, I will be using symbols:

  • ✔️ represents something I liked
  • ⚠️ can represent several things:
    • something that caused me problems because it's different from what I am used to
    • something that could be improved
    • areas where user need to be careful
  • ℹ represents something that wasn't obvious for me at first but is not directly related to a problem due to Feliz

The same entry can have several symbols :)

Feliz


✔️ Feliz and Fable.React can be mixed because Feliz is a layer on top of Fable.React


✔️ The code is more comfortable to indent compared to Fable.React because we only have a single list. It is easier to follow the tabulation.

Click here for details

Fable.React + Fulma

div [ ]
    [ Hero.hero [ Hero.IsFullHeight ]
        [ Hero.body [ ]
            [ Container.container [ ]
                [ img [ Src "img/fable-ionide.png"
                        Style [ Display DisplayOptions.Block
                                Width "auto"
                                Margin "auto" ] ]
                  br [ ]
                  Heading.h3 [ Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
                    [ str "Fable REPL" ]
                  Heading.p [ Heading.IsSubtitle
                              Heading.Is5
                              Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
                    [ str "is only available on desktop" ] ] ] ] ]

Feliz + Feliz.Bulma

Html.div [
    Bulma.hero [
        hero.isFullHeight
        prop.children [
            Bulma.heroBody [
                Bulma.container [
                    Html.img [
                        prop.src "img/fable-ionide.png"
                        prop.style [
                            style.display.block
                            style.width length.auto
                            style.margin length.auto
                        ]
                    ]

                    Html.br [ ]

                    Bulma.title3 [
                        text.hasTextCentered
                        prop.text "Fable REPL"
                    ]

                    Bulma.subtitle5 [
                        text.hasTextCentered
                        prop.text "is only available on desktop"
                    ]
                ]
            ]
        ]
    ]
]


✔️ Thanks to the previous point it's also easy to move code around

feliz_move_code_demo


✔️ Strongly typed API for the DOM properties


✔️ Strongly typed API for CSS and CSS units via ICSSUnits

style.marginLeft (length.em 0.5)
style.width (length.percent (model.PanelSplitRatio * 100.))

✔️ Doesn't pollute the context, most of the things are under Html.* or Prop.*


✔️ ⚠️ Feliz offers the possibility to avoid noise in the code

Click here for details

Html.span "Fable REPL"

// instead of

// Feliz verbose version
span [
    str "Fable REPL"
]

// Fable.React

span [ ]
    [ str "Fable REPL" ]

// --------------------

Bulma.title4 "Fable REPL"

// instead of

// Feliz.Bulma verbose version
Bulma.title4 [
    prop.text "Fable REPL"
]

// Fable.React
Headings.h4 [ ]
    [ str "Fable REPL" ]

Because there are several ways to write the same code, we can't just "read" the code. We sometimes need to take a step back to understand the outer context.
This also makes the code less "consistent" because not everything is written the same way.

Html.tr [
    Html.th "Steps"
    Html.th [
        prop.className "has-text-right"
        prop.text "ms"
    ]
]


⚠️ It doesn't support (yet) SSR


⚠️ Discovering the methods/properties overload is not easy (this is perhaps a limitation of Ionide I don't know)

I had to:

  • experiment by myself and check the compiler error. For example, testing if prop.text 2.0 is supported or not when I had a float.
  • look directly at Feliz source code

⚠️ Make harder the use of callback taking more than one parameter. In Fable.REPL, I had to force the uncurry by using System.Func<_,_,_>.

Click here for details

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>) = Interop.mkAttr "editorDidMount" f

Which gives this code on the calling side:

let private registerCompileCommand dispatch =
    System.Func<_,_,_>(
        fun (editor : Monaco.Editor.IStandaloneCodeEditor) (monacoModule : Monaco.IExports) ->
            let triggerCompile () = StartCompile None |> dispatch
            editor.addCommand(monacoModule.KeyMod.Alt ||| int Monaco.KeyCode.Enter, triggerCompile, "") |> ignore
            editor.addCommand(monacoModule.KeyMod.CtrlCmd ||| int Monaco.KeyCode.KEY_S, triggerCompile, "") |> ignore
    )


✔️ ⚠️ Feliz provides overloads for making user life a bit easier, but this has a cost.

For prop.onChange, as like 6 overloads depending on what you want to listen:

  • static member inline onChange (handler: bool -> unit)
  • static member inline onChange (handler: Event -> unit)
  • static member inline onChange (handler: File -> unit)
  • static member inline onChange (handler: File list -> unit)
  • static member inline onChange (handler: string -> unit)
  • static member inline onCheckedChange (handler: bool -> unit)

This is nice because this avoids people to write "no" fun code, but the counterpart is you need to say which Event type you want explicitly.

// Feliz
prop.onChange (fun (e : Types.Event) -> e.Value |> ChangeGistToken |> dispatch)

// Fable.React
prop.onChange (fun e -> e.Value |> ChangeGistToken |> dispatch)

✔️ ⚠️ Feliz ecosystem is mostly type-safe but doesn't prevent writing invalid code. When using Feliz and one of its extension like Feliz.Bulma you can easily mix properties but you need to be careful when doing it.

Click here for details

This code seems OK from Feliz and the F# compiler point of view but it will not give you the expected result.

Html.p [
    text.isUppercase
    text.isItalic
    color.hasTextSuccess
    prop.text "Hello Feliz"
]

text.isUppsercase, text.isItalic and color.hasTextSuccess all output something like ClassName "my-css-class. But in React, only the last one will have an effect so in our case the code will generate:

<p class="has-text-success">Hello Feliz<\p>

instead of

<p class="is-uppercase is-italic has-text-success>Hello Feliz</p>

The solution to that is to use the ++ offered by Feliz.Bulma:

open Feliz.Bulma.Operators

Html.p [
    text.isUppercase
    ++ text.isItalic
    ++ color.hasTextSuccess
    prop.text "Hello Feliz"
]

This is still a nice possibility because that's means you can add behaviour coming from another Feliz library to a "Feliz element" if they are compatible. People just need to be careful about it.


ℹ At first I thought Feliz didn't offer the syntax sugar ev.Value that we get when using Fable.React, but that's not the case.

Fable.React.Extension host the syntax sugar, so we can just open it to not pollute the context with all the Fable.React functions.

open Feliz
open Fable.React.Extensions

ℹ The syntax doesn't reproduce the HTML way of thinking. Feliz is more a syntax sugar on top of React API than HTML. Feliz thinks in term of properties because even children is a property.


Feliz.Bulma

✔️ Integrates well with Feliz


✔️ Easier to indent compared to Fulma

Click here for details

// Fulma
Card.card [ ]
    [ Card.header [ Common.Props [ OnClick (fun _ -> ToggleWidget title |> dispatch ) ] ]
        [ Card.Header.title [ ]
            [ Icon.icon [ Icon.Props [ Style [ MarginRight ".5em" ] ] ]
                [ Fa.i [ icon; Fa.Size Fa.FaLarge ] [] ]
                str title ]
            Card.Header.icon [ ]
            [ Icon.icon [ ] [ Fa.i [ headerIcon ; Fa.Size Fa.FaLarge ] [] ] ] ]
        ofOption content ]

// Feliz.Bulma
Bulma.card [
    Bulma.cardHeader [
        prop.onClick (fun _ -> ToggleWidget title |> dispatch )
        prop.children [
            Bulma.cardHeaderTitle [
                Bulma.icon [
                    prop.style [
                        style.marginRight (length.em 0.5)
                    ]
                    prop.children [
                        Fa.i [ icon; Fa.Size Fa.FaLarge ] []
                    ]
                ]
                Html.text title
            ]

            Bulma.cardHeaderIcon [
                Bulma.icon [
                    Fa.i [ headerIcon ; Fa.Size Fa.FaLarge ] []
                ]
            ]
        ]
    ]

    Html.ofOption content
]


✔️ It's easy to identify Bulma components thanks to Bulma prefix


✔️ ⚠️ The properties are accessible but pollute the context button.*, help.*, columns.*.

In theory, people should only use one CSS framework, so I don't think they will have a clash on generic properties like button.*, columns.*, etc.


✔️ The component hierarchy seems easy enough to grabs

Bulma.card > Bulma.cardHeader > Bulma.cardHeaderTitle

Click here for example

Bulma.card [
    Bulma.cardHeader [
        prop.onClick (fun _ -> ToggleWidget title |> dispatch )
        prop.children [
            Bulma.cardHeaderTitle [
                // ...
            ]

            Bulma.cardHeaderIcon [
                // ...
            ]
        ]
    ]
]


⚠️ But some of the components don't follow the same convention

Bulma.passwordInput instead of Bulma.inputPassword

In this case, you can't easily explore the different type of input you have because they don't start with the same "prefix".


✔️ ⚠️ Feliz.Bulma makes it easy to mix components behaviours.

Click here for details

Bulma.button [
    // Properties specific to a button coming from Feliz.Bulma
    button.isOutlined
    // Properties specific to a tooltip coming from Feliz.Bulma
    tooltip.hasTooltipRight
    tooltip.text tooltipText
    // General properties coming from Feliz
    prop.disabled isCompiling
    prop.onClick (fun _ -> dispatch msg)
    prop.children [
        Bulma.icon [
            icon.isLarge
            prop.children faIcon
        ]
    ]
]

That's nice because as you see, it's easy to add new behaviour to an existing component. Here we added the behaviour tooltip to a button. But this also means you can write invalid code like:

Html.select [
    select.isFullwidth
]

instead of

Bulma.select [
    select.isFullwidth
]

Fulma is more strict about component separation and doesn't allow you to mix the behaviour unless you pass the CSS classes via CustomClass props.


✔️ ⚠️ Feliz.Bulma doesn't provide a way for controlling which HTML element we want to output.

For example, we only have Bulma.field which generates a div. However, sometimes you want a p element as the output.


✔️ ⚠️ Feliz.Bulma is an exact mapping over Bulma

This is nice because it's slim.

But it also means:

  • You need to know Bulma well enough or have Bulma documentation opened
  • It's doesn't avoid noisy code

For example, when writing a Bulma.tabs components you are not guided and need to know that tabs need ul followed by li with an a in it.

Click here to see the code

// Feliz + Feliz.Bulma
Bulma.tabs [
    tabs.isCentered
    tabs.isMedium
    tabs.isToggle
    prop.children [
        Html.ul [
            Html.li [
                if (activeTab = CodeTab.FSharp) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.FSharp |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "F#"
                    ]
                ]
            ]

            Html.li [
                if (activeTab = CodeTab.Html) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.Html |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "HTML"
                    ]
                ]
            ]

            Html.li [
                if (activeTab = CodeTab.Css) then
                    prop.className "is-active"
                prop.onClick (fun _ -> SetCodeTab CodeTab.Css |> dispatch)
                prop.children [
                    Html.a [
                        prop.text "CSS"
                    ]
                ]
            ]
        ]
    ]
]

Fulma allows the user to think more in term of "Bulma components".

// Fable.React + Fulma
    Tabs.tabs [ Tabs.IsCentered
                Tabs.Size Size.IsMedium
                Tabs.IsToggle ]
        [ Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.FSharp)
                     Tabs.Tab.Props [
                        OnClick (fun _ -> SetCodeTab CodeTab.FSharp |> dispatch)
                     ] ]
            [ a [ ] [ str "F#" ] ]
          Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.Html)
                     Tabs.Tab.Props [
                         OnClick (fun _ -> SetCodeTab CodeTab.Html |> dispatch)
                     ] ]
            [ a [ ] [ str "HTML" ] ]
          Tabs.tab [ Tabs.Tab.IsActive (activeTab = CodeTab.Css)
                     Tabs.Tab.Props [
                         OnClick (fun _ -> SetCodeTab CodeTab.Css |> dispatch)
                     ] ]
            [ a [ ] [ str "CSS" ] ] ]

Note:

Fulma considers tab as a component and offers Tabs.Tab.* specific wrapper.

Fulma still needs you to know that an a element is necessary, but we could add it by default (that's the case for some components).


✔️ ⚠️ Feliz.Bulma has a smaller impact on the bundle size on a small project but will have a more significant impact when the project size increase.

Click here for details

Fulma is using a lot of DUs to model Bulma classes. You can take a look at Common.fs; also each component has its own DUs.

This requires Fulma to implements a function call parseOptions which in simple terms convert the DUs into classes.

Feliz.Bulma takes a more direct approach by not creating a DSL on top of Bulma classes but instead directly output the classes.

// Fulma
Column.column
    [
        Column.Width (Screen.Desktop, Column.IsHalf)
        Column.Width (Screen.Mobile, Column.IsFull)
    ]
    [
        // ...
    ]

// Feliz
Bulma.column [
    column.isHalfDesktop
    column.isFullMobile
    prop.children [
        // ...
    ]
]

Thanks to the direct class usage and the manipulation of the property list Feliz.Bulma doesn't have the big cost of all the DUs and code added in Fulma. However, it still needs an extra pass to "join" all the classes.

This part is done via Feliz.Bulma.ElementBuilders.Helpers module but all these functions are inlined. That's why the bigger your project, the bigger the impact on your code.


⚠️ It is not easy to add new colour support.

In Feliz.Bulma, each component holds it's own colour implementation like button.isWarning, help.isWarning, etc.

So if you want to add your colour, you need to implement all button.isMyNewColor, help.isMyNewColor

In Fulma, they all share the same color type. See the documentation


Fable.React

✔️ Follows HTML structure when you think of it as a list of properties and a list of children


⚠️ Indentation rules are hard to establish because we need to organise a double list and in general, we make a lot of exceptional cases.

Click here for a detailed explanation

Example:

// When I want to put an empty div
div [ ] [ ]

// When I want to put a div with a single property
div [ ClassName "button" ] [ ]
// or
div [ ClassName "button" ]
    [ ]

// When I want to put a div with several property
div [ ClassName "button"; Id "my-button" ]
    [ ]
// yes but what if one of my property is not that simple?
div
    [
        ClassName "button"
        OnClick (fun _ ->
            // do something
        )
    ]
    [ ]

// It's also possible to have consistent indentation if you do something like that
div [ ] [
    // Children 1
    // Children 2
]

// But if you have non simple property you still have problems, I don't personally don't find it easy to read
// I am not sure if that's how people would write it because I don't use this format personally
div [
    ClassName "button"
    OnClick (fun _ ->
        // do something
    )
] [
    div [ ] [
        str "Children 1"
    ]
]

// and so on

As you can see just when dealing with a few cases on the property list, we have several possible syntax.

If I had to establish a consistent syntax in my project, it would be something like that:

div
    [
        // Property 1 ...
        // Property 2 ...
        // Property 3 ...
    ]
    [
        div
            [
                // Property 1 ...
                // Property 2 ...
                // Property 3 ...
            ]
            [
                // Children 1 ...
                // Children 2 ...
                // Children 3 ...
            ]
        div
            [
                // Property 1 ...
                // Property 2 ...
                // Property 3 ...
            ]
            [
                // Children 1 ...
                // Children 2 ...
                // Children 3 ...
            ]
    ]

// So for an empty element
div
    [

    ]
    [

    ]

// Awesome... 🙄

For comparaison in Feliz, I do:

// Empty div
Html.div [ ]

// Non empty div
Htmldiv [
    // Property 1
    // Property 2
    // Property 3
    prop.children [
        Html.div [
            // Property 1
            // Property 2
            // Property 3
        ]

        Html.div [
            // Property 1
            // Property 2
            // Property 3
            // Complexe property
            OnClick (fun _ ->
                // Do something
            )
        ]
    ]
]

So I have only 2 cases now, and I could even write the empty version on multiple lines without too much noise if I really want a single way of structuring Feliz code.


⚠️ Most of the API is not typed

type HTMLAttr =
    | DefaultChecked of bool
    | DefaultValue of obj
    | Accept of string
    | AcceptCharset of string
    | AccessKey of string
    | Action of string
    | AllowFullScreen of bool
    | AllowTransparency of bool

✔️ ⚠️ Some of the API is typed but not all, which means that the code is not consistent.


✔️ It supports SSR


Fulma

✔️ Type-safe API

Modifier.TextAlignment (Screen.All, TextAlignment.Centered)

Button.button [ Button.Color IsWhite ]
    [ str "White" ]

✔️ Fulma API is easy to explore via intellisense when you understand how it's structured


✔️ Fulma force you to think in term for components


✔️ Make it easy to extends the supported colour thanks to IsCustomColor

// All you need to add `custom-purple` support to all your components is this line
let isCustomPurple = IsCustomColor "custom-purple"

✔️ ⚠️ I think it has good documentation with examples for all the components, but not every component contains the same level of information


✔️ ⚠️ Fulma impact on your bundle size is "stable". This point has been described in Feliz.Bulma section


⚠️ People don't find "Special helpers" easily.

  • CustomClass
  • Props
  • Modifiers

⚠️ Fulma code add noise to your code because of the strongly typed DSL

Click here for an example

// Fulma
Heading.p [ Heading.IsSubtitle
            Heading.Is5
            Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ]
    [ str "is only available on desktop" ]

// Feliz.Bulma
Bulma.subtitle5 [
    text.hasTextCentered
    prop.text "is only available on desktop"
]


⚠️ Fulma code is not that easy to format correctly because it has a lot of nested list when you combine Props, Modifiers


I have been working on this analysis on a period of 2-3 weeks and could still continue. But I have to end it because it's taking a lot of time and I think I now have a better view of the situation.

All that to say, that there could be more good points or problems to list especially about Fable.React and Fulma but that also the hardest part for me. I have been using them and designing them for more than 3 years so it's hard to have an objective view on them.

Was the conversion difficult?

The conversion from Fable.React to Feliz was easy enough to do. Feliz being compatible with Fable.React you can do the conversion in a progressive way file per file, component per component, etc.

To be sure I converted everything I removed all the open Fable.React and open Fable.React.Props instructions. I also removed Fulma as a dependency.

Of course, I had forgotten some files so I fixed the compiler errors and then it was working.

You can see the merge request hosting the conversion here.


My journey with Feliz

When discovering Feliz at first, I was on the defensive because it was not following the "HTML/JSX way" and it's an alternate project for things I invested a lot of effort in.

Then I remembered that it's how innovation works and that what allows us to grow. For example, thanks to that that we have Elmish today.

Another thing I remembered is that JSX is not the "real React API", indeed React API is React.createElement. JSX is a syntax sugar on top of it, the same thing goes for Fable.React and Feliz. I insist on it because for me it helped me move forward and be curious about it.

To test Feliz, I decided to use a medium size project which is the Fable REPL. Fable REPL is not a complex application but it uses a lot of feature from Fable and React.

A non-exhaustive list of things I have tested thanks to this decision:

  • Feliz
  • Feliz.Bulma
  • How well Feliz and Fable.React are compatible
  • How easy it is to make the transition from Fable.React to Feliz
  • Usage of a functional component with a native call to the DOM from Feliz
  • How to write a binding and interop with non-trivial custom component written in JavaScript the Feliz way
  • The transition from Fulma to Feliz.Bulma so I can see what a Fulma equivalent could be in Feliz ecosystem

When starting the conversion I was really pleased with how easy it was to format the code. I think the convention for writing Feliz based view could be really easy to follow. The bigger my applications grow, the more I find code formatting important.

Then in the middle, I started to be annoyed with something but couldn't find what.

I finished converting Fable REPL to Feliz, and wasn't sure if I liked or not my experience with it.

It's only now, that I trying to conclude my analysis, that I understand what holds me back. It is not much related to Feliz but more to Feliz.Bulma.

It's important to remember that for more than 2.5 years I have been working on Fulma and improving it. Fulma is one of my biggest projects. Writing code with it is great especially because everything is typed and it forces you to think in term of components.

Feliz.Bulma takes a different approach and is younger than Fulma; it's only 5 months old.

With all that said, I think it's now time to move to my conclusion :)

Conclusion

I think Feliz biggest advantage is the single list style. That's something I mentioned a lot but for me being able to format easily my code and not having to think where I should start the next line is a big plus.

Secondly, the type-safe API is a must-have. It is more verbose than the CSS equivalent but I think it's a good feature. I didn't do fancy stuff with it so I can't say if everything is supported but if that's not the case we can always make a PR to fix that :).

Feliz.Bulma shows a promising way to extends Feliz but is not yet mature enough to be a direct equivalent to Fulma. This is something I want to explore more with @Dzoukr and I hope we will be able to found a middle ground between the simplicity of Feliz.Bulma and the completeness but verbosity of Fulma.

3 years ago, the 8 Mar 2017, Tomas, Eugene and me decided to merge Fable.Arch with Elmish. This allowed us as a community to build the amazing ecosystem we have today.

I think we are currently living a similar period with Feliz and Fable.React. I think Feliz will gain more traction than Fable.React
but both will co-exist together. In the end, both of them works well together this means people can choose the one they prefer to use.

Thank you for reading my analysis and I will be happy to discuss it with you in the comment section :)

@l3m
Copy link
Contributor

l3m commented Apr 7, 2020

Great analysis.

For me personally, even though the double list formatting is a pain sometimes, I feel that looking at Fable.React code I get to see the structure of the underlying html better. It's div [.. , not Html.div [.... (etc). It's not a big thing, but my brain seems to have an easier parsing Fable.React.

@Shmew
Copy link
Collaborator

Shmew commented Apr 7, 2020

Great write up @MangelMaxime, you made a lot of good points.

From what I've gathered the main takeaway of this is mostly issues with how the library is extended, and the problems that occur when not done optimally.

I can definitely agree that writing libraries for Feliz places a lot more of the work on the developer, but in my opinion it's worth the effort when the resulting api is so nice to use.

I've written a fair number of extensions for Feliz so I think I can provide some insight on how to mitigate a lot of these issues:

Overloads

I use Visual Studio and have zero problems finding which overloads are available to me, maybe this is something that can be improved on ionide's end?

We could just make prop.text be a single inline function that requires the member toString and accept any type?

Callbacks:

Instead of:

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>) = Interop.mkAttr "editorDidMount" f

Write:

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : Monaco.Editor.IStandaloneCodeEditor -> Monaco.IExports -> unit) = Interop.mkAttr "editorDidMount" (Func<_,_,_> f)

You can then call the property like you would expect on the client side.

Writing invalid code

I haven't seen anyone else apply this quite to the extent I have in some of my libraries, and may not be entirely applicable when extending Html. The way you prevent this is by adding additional interface types to the properties you define, in my largest Feliz library Feliz.Plotly I have it setup in such a way that you will get a compiler error if you try to use a property that is not valid. The only downside is it's quite a bit of work for the library developer.

Click to expand
[<AutoOpen;EditorBrowsable(EditorBrowsableState.Never)>]
module Types =
  type IPlotProperty = interface end
  type IModeBarButtonsProperty = interface end
  type IAaxisProperty = interface end
  type IAggregateProperty = interface end
  type IAggregationProperty = interface end
  type IAggregationsProperty = interface end
  type IAngularaxisProperty = interface end
  type IAnimationProperty = interface end
  type IAnnotationProperty = interface end
  type IAnnotationsProperty = interface end
  type IAreaProperty = interface end
  type IAspectratioProperty = interface end
  type IAxisProperty = interface end
  type IBarProperty = interface end
  type IBarpolarProperty = interface end
  type IBaxisProperty = interface end
  type IBorderProperty = interface end
  type IBoxProperty = interface end
  type IButtonProperty = interface end
  type ICameraProperty = interface end
  type ICandlestickProperty = interface end
  type ICapsProperty = interface end
  type ICarpetProperty = interface end
  type ICaxisProperty = interface end
  type ICellsProperty = interface end
  type ICenterProperty = interface end
  type IChoroplethProperty = interface end
  type IChoroplethmapboxProperty = interface end
  type ICircleProperty = interface end
  type IColoraxisProperty = interface end
  type IColorbarProperty = interface end
  type IColorscaleProperty = interface end
  type IColorscalesProperty = interface end
  type IConcentrationscalesProperty = interface end
  type IConeProperty = interface end
  type IConfigProperty = interface end
  type IConnectorProperty = interface end
  type IContourProperty = interface end
  type IContourcarpetProperty = interface end
  type IContoursProperty = interface end
  type ICumulativeProperty = interface end
  type ICurrentvalueProperty = interface end
  type IDecreasingProperty = interface end
  type IDeltaProperty = interface end
  type IDensitymapboxProperty = interface end
  type IDiagonalProperty = interface end
  type IDimensionProperty = interface end
  type IDimensionsProperty = interface end
  type IDomainProperty = interface end
  type IEditsProperty = interface end
  type IErrorXProperty = interface end
  type IErrorYProperty = interface end
  type IErrorZProperty = interface end
  type IEyeProperty = interface end
  type IFillProperty = interface end
  type IFilterProperty = interface end
  type IFontProperty = interface end
  type IFrameProperty = interface end
  type IFramesEntryProperty = interface end
  type IFramesProperty = interface end
  type IFunnelProperty = interface end
  type IFunnelareaProperty = interface end
  type IGaugeProperty = interface end
  type IGeoProperty = interface end
  type IGradientProperty = interface end
  type IGridProperty = interface end
  type IGroupbyProperty = interface end
  type IHeaderProperty = interface end
  type IHeatmapProperty = interface end
  type IHeatmapglProperty = interface end
  type IHistogram2dProperty = interface end
  type IHistogram2dcontourProperty = interface end
  type IHistogramProperty = interface end
  type IHoverlabelProperty = interface end
  type IImageProperty = interface end
  type IImagesProperty = interface end
  type IIncreasingProperty = interface end
  type IIndicatorProperty = interface end
  type IInsidetextfontProperty = interface end
  type IIsosurfaceProperty = interface end
  type ILabelfontProperty = interface end
  type ILataxisProperty = interface end
  type ILayerProperty = interface end
  type ILayersProperty = interface end
  type ILayoutProperty = interface end
  type ILeafProperty = interface end
  type ILegendProperty = interface end
  type ILightingProperty = interface end
  type ILightpositionProperty = interface end
  type ILineProperty = interface end
  type ILinkProperty = interface end
  type ILonaxisProperty = interface end
  type IMapboxProperty = interface end
  type IMarginProperty = interface end
  type IMarkerProperty = interface end
  type IMeanlineProperty = interface end
  type IMesh3dProperty = interface end
  type IModebarProperty = interface end
  type INodeProperty = interface end
  type INumberProperty = interface end
  type IOhlcProperty = interface end
  type IOutsidetextfontProperty = interface end
  type IPadProperty = interface end
  type IParcatsProperty = interface end
  type IParcoordsProperty = interface end
  type IPathbarProperty = interface end
  type IPieProperty = interface end
  type IPointcloudProperty = interface end
  type IPolarProperty = interface end
  type IProjectProperty = interface end
  type IProjectionProperty = interface end
  type IRadialaxisProperty = interface end
  type IRangebreakProperty = interface end
  type IRangebreaksProperty = interface end
  type IRangefontProperty = interface end
  type IRangeselectorProperty = interface end
  type IRangesliderProperty = interface end
  type IRotationProperty = interface end
  type ISankeyProperty = interface end
  type IScatter3dProperty = interface end
  type IScatterProperty = interface end
  type IScattercarpetProperty = interface end
  type IScattergeoProperty = interface end
  type IScatterglProperty = interface end
  type IScattermapboxProperty = interface end
  type IScatterpolarProperty = interface end
  type IScatterpolarglProperty = interface end
  type IScatterternaryProperty = interface end
  type ISceneProperty = interface end
  type ISelectedProperty = interface end
  type IShapeProperty = interface end
  type IShapesProperty = interface end
  type ISlicesProperty = interface end
  type ISliderProperty = interface end
  type ISlidersProperty = interface end
  type ISortProperty = interface end
  type ISpaceframeProperty = interface end
  type ISplomProperty = interface end
  type IStartsProperty = interface end
  type IStepProperty = interface end
  type IStepsProperty = interface end
  type IStreamProperty = interface end
  type IStreamtubeProperty = interface end
  type IStyleProperty = interface end
  type IStylesProperty = interface end
  type ISunburstProperty = interface end
  type ISurfaceProperty = interface end
  type ISymbolProperty = interface end
  type ITableProperty = interface end
  type ITernaryProperty = interface end
  type ITextfontProperty = interface end
  type IThresholdProperty = interface end
  type ITickfontProperty = interface end
  type ITickformatstopProperty = interface end
  type ITickformatstopsProperty = interface end
  type ITilingProperty = interface end
  type ITitleProperty = interface end
  type ITotalsProperty = interface end
  type ITracesProperty = interface end
  type ITransformsProperty = interface end
  type ITransitionProperty = interface end
  type ITreemapProperty = interface end
  type IUniformtextProperty = interface end
  type IUnselectedProperty = interface end
  type IUpProperty = interface end
  type IUpdatemenuProperty = interface end
  type IUpdatemenusProperty = interface end
  type IViolinProperty = interface end
  type IVolumeProperty = interface end
  type IWaterfallProperty = interface end
  type IXProperty = interface end
  type IXaxisProperty = interface end
  type IXbinsProperty = interface end
  type IYProperty = interface end
  type IYaxisProperty = interface end
  type IYbinsProperty = interface end
  type IZProperty = interface end
  type IZaxisProperty = interface end
  type IButtonsProperty = inherit IModeBarButtonsProperty
  type IMeasureProperty = interface end
  type ITemplateProperty = interface end

In a similar vein, you can also implement things in the library code to "automagically" combine the properties if you want. I'm not sure how this works when using things outside your own library though. The trick to this is to make those items a different type than just IReactProperty. For example in Feliz.Plotly I do a lot of things where depending on configurations given I will modify/collect/change the users input to properly translate it to what the actual code needs to look like. It is really nice in some respects for removing things that are always present.

Conclusion

I think much of the pain you had could be solved with better documentation on how to navigate writing your own libraries in the "Feliz" way. Maybe @Zaid-Ajaj and I can work on that in the future.

@MangelMaxime
Copy link
Contributor Author

Thank you @l3m

For me personally, even though the double list formatting is a pain sometimes, I feel that looking at Fable.React code I get to see the structure of the underlying html better. It's div [.. , not Html.div [.... (etc). It's not a big thing, but my brain seems to have an easier parsing Fable.React.

Indeed, switching how to read the code is not easy and that's why I mentioned we should not see it as HTML in my case it's helped me make the switch.

I suppose that with F# 5 and the ability to "open static classes" Feliz will be able to support both Html.div and div syntax depending on if you open or not the Html class.

Thank you @Shmew for the comment and indeed I forgot that there are several types of "libraries" for Feliz.

About the "Writing invalid code" yes but in some libraries, we do want to "extends" IReactProperty type in order to avoid the Fulma situation where you need to pass standard properties via a special helper like Props. The reason being that Bulma "components" are just a specialization over the DOM elements.

They are just code that injects a class by default in the HTML element.

Bulma.button generates <div class="button">

In the case of a library which works on "real" components (sorry don't know how to name it differently) your solution is indeed the right one. For example, this is the approach taken by Fable.ReactLeaflet where I am defining the specific properties for each component. And thus components don't have to support all the standard HTML properties.

About your solution for "Callbacks" not it didn't work for me. But the function passed by Fable was having curried arguments and unless I wanted to write something like myFunction(arg1)(arg2) then I had to force the uncurried version via System.Func.

If you know how to make it work I will be happy to see a PR sent to fix it in https://github.com/fable-compiler/repl/pull/108/files

@Zaid-Ajaj
Copy link
Owner

Hi @MangelMaxime,

First of all, I want to thank you for taking the time and effort not just to try out Feliz on a project but also to write such an extensive analysis. I am sure many of Fable users will find it very helpful and informative.

I really appreciate that this kind of feedback and openness and believe it is crucial for to make this library the best it can be along with its ecosystem 🙏

I will talk about Feliz.Bulma in a moment because I think it is kind of special in the ecosystem, First I will answer to some issues you have that can already be fixed without changes in the library.

Overloads of onChange

you need to say which Event type you want explicitly.

Actually, you don't. The type should be inferred from your message type as follows:

prop.onChange (ChangeGistToken >> dispatch)

Depending on the type of ChangeGistToken, the correct overload will be inferred. When the DU case is ChangeGistToken of string then the overload of string -> unit will be chosen. But it can also be FileSelected of File when input type is file in which case the overload File -> unit will be inferred and so on and so forth. You don't need the syntax sugar of ev.Value from Fable.React

prop.onCheckedChange will be removed because onChange handles the boolean case already

Multi-argument callbacks

@Shmew has already touched upon the issue and indeed that is the way to solve these types of properties. It is crucial to understand that when implementing Feliz extensions, you can do transformations before setting the property:

type myExtension = 
    static member inline extenstionProperty value = 
        // transform value here
        let transformedValue = doWeirdStuffWith value
        Interop.mkAttr "extenstionProperty" transformedValue

Following this logic, you could convert 'A -> 'B -> unit into Func<'A, 'B, unit> internally:

[<Erase>]
type editor =
    /// <summary>Triggered when the Editor has been mounted</summary>
    static member inline editorDidMount (f : Monaco.Editor.IStandaloneCodeEditor -> Monaco.IExports -> unit) = 
        let transformedFunction = System.Func<Monaco.Editor.IStandaloneCodeEditor, Monaco.IExports, unit>(fun editor exported -> f editor exported)
        Interop.mkAttr "editorDidMount" transformedFunction

This is a more explicit version than the one provided by @Shmew (As you know with bindings, you always have to play with them a bit to get them right). A working example of this is here

Type-Safety in the Feliz ecosystem

As @Shmew mentioned above: a proper Feliz extension doesn't allow you to write code that behaves incorrectly and constraining the types of properties allow you to implement that. You can either build properties that return IReactProperty like Feliz.Recharts does which allow for backward-compatibility with existing properties in prop or you could implement a more specialized type ISpecialiedProperty to support only a subset of the properties while duplicating the names of some properties from IReactProperty to ensure backward-compatibility:

type felizExtension = 
    static member inline specializedProp (value: string) : ISpecializedProperty = unbox (prop.text value) 

This what I have been doing with Feliz.AntDesign, for example here where I am duplicating some properties to make available from the button entry point (Later on, I could make the properties more specialized with strict types but at first, backward-compat is where you first start). I believe Feliz.MaterialUI does similar things to extend IReactProperty rather than specializing properties. This brings to the case of Feliz.Bulma.

The case of Feliz.Bulma

Before Feliz.Bulma was implemented, I had in my mind the idea that there is no need to build Feliz extensions for CSS-only frameworks. Feliz makes it already very easy to compose multiple class names and work with them conditionally. Now with implicit yield in F# it is even easier. Combine this with TypedCssClasses and you remove the need of implementing any CSS-only framework. Zero implementation and maintenance costs. Just use the typed stylesheet and use the already really good documentation of the project itself instead of having to build a documentation website yourself.

However, after I saw the implementation of Feliz.Bulma by @Dzoukr it made sense: you could simplify the code a lot by introducing specific "entry points" to the CSS components from one module of Bulma so that you get Bulma.button, Bulma.card etc. These are really nice to use. As for the modifiers, I think Roman's approach is easy to follow and to work with but like you said, sometimes it could give interesting results. Surely, there is more to improve in that department and in any case I think Roman did a great job at following the vision of Feliz into building something easy to learn and to use.

Package management of Feliz

I just want to point out a huge difference in current Fable.React and Feliz: the package versioning and management. Feliz versions are all of its ecosystem libraries are Femto-compatible which removes the burden from users of having to manually install required npm packages or knowing which version they are compatible with. This is unlike many if not all derivative Fable.React libraries and even Fable.React itself where that is not the case.

Future Plans

  • Writing (even more) documentation on how to build Feliz-style extensions and what should adhere to.
  • Building a Feliz analyzer for Feliz itself but possibly for its ecosystem

@MangelMaxime
Copy link
Contributor Author

MangelMaxime commented Apr 7, 2020

Actually, you don't. The type should be inferred from your message type as follows:

prop.onChange (ChangeGistToken >> dispatch)

Indeed, I didn't think about this usage.

Multi-argument callbacks

@Shmew has already touched upon the issue and indeed that is the way to solve these types of properties.

Oh sorry, I didn't saw the end of the snippet provided by @Shmew. I just saw the signature declaration and was like no doesn't work.

You are both right, we can do the transformation before passing the value to the property.

The case of Feliz.Bulma

Yes, TypedCssClasses is ok but compared to Fulma or Feliz.Bulma it can't provide the same level of features. Because not everything is contained in the CSS file.

Another thing is that in dev mode, we don't always have the file generated on disk and I am not sure if TypedCssClasses support this scenario.

I think Roman did a great job at following the vision of Feliz into building something easy to learn and to use.

I do agree, I just think we need to add a bit of the work done in Fulma to make it similar. Like I said we need to find the correct middle ground between both of them.

The missing features I have in mind right now are allowing to control the output element and make it easier to extends the set of supported colour. I don't think it will add much complexity over it.

@Zaid-Ajaj thank you for the feedback and especially for pointing me the right direction on how to use Feliz potential.
I admit I didn't really read Feliz documentation (that's bad I know) so perhaps they are documented in it. If not, I guess we know a few more things needed to add :)

@theimowski
Copy link

theimowski commented Apr 7, 2020

Thanks @MangelMaxime - that's a great comparison. You haven't mentioned Fantomas anywhere for auto formatting the code - did you have a chance to work with Fantomas with either Fable.React or Feliz?
In one of my Fable projects where I use Fantomas, I turned on auto-formatting on file save in Ionide and must say it's really a productivity booster - no need to worry about manual formatting at all!
I really think Fantomas is getting its momentum now and would love to see its wider adoption, also in conjunction with Fable and React / Feliz.

@MangelMaxime
Copy link
Contributor Author

@theimowski I didn't use Fantomas since some times.

Mostly because it was messing with my code and making it really hard to read. From my experience to was not playing well with Fable.React and Feliz.

I also don't like the default of Fantomas I need to check it again in the future when I know how I want my code to be indented. But, from what I looked at the repo I don't think it will fit my needs mostly because of Fantomas formatting the code depending on the size of context.

For example, if I write something like that:

type Test =
    {
        Prop1 : string
    }

I do want it to stay like that and not become type Test = { Prop1 : string } just because it can fit on a single line.

Perhaps a record is not a good example, but I hope you understand what I mean :)

@Shmew
Copy link
Collaborator

Shmew commented Apr 7, 2020

@theimowski that's great to hear, I use fantomas on my non-Fable projects. I'll have to give it another shot and see how it handles it.

@albertwoo
Copy link

@MangelMaxime thanks for your work. Somehow your Fulma project attacked me to the fable world. And I really enjoy the journey.

I also like the single list but I also like DU than classes member overrides. Sometimes I wonder if DU supports override then it would be super great.
Because I already use Fable.React a lot but still want single list, so I created a operator </> and I can use it like:

let myView =
    div </> [
        Classes [ ""; ""; "" ]
        Children [
              ...
        ]
    ]

The operator is pretty simple and also take advantage of DU, you can check my rough implementation here: https://github.com/albertwoo/Fun.LightForm/blob/master/src/Fun.LightForm.Fable/Fable.React.Extra.fs

@Zaid-Ajaj Thanks for your great work too, I also started to convert some of my projects to Feliz and Feliz.Material.

Thanks!

@Dzoukr
Copy link
Contributor

Dzoukr commented Apr 8, 2020

Hi @MangelMaxime,
first of all thank you! Frankly I wasn't expecting someone would have such deep look at library I created mainly for dogfooding own projects and published as side effect. Funny how initial thought of "hey, how @Zaid-Ajaj designed Feliz is great - let's do the same for Bulma" swollen up.

I already created new Issue in Feliz.Bulma repo and love to discuss how to make v2 even better. It seems we have agreement on most of issues, so it shouldn't be any problem to start version2 branch. Also looking at what are Zaid's plan for next major release could be handy to keep API between latest major versions 100% compatible.

Anyway, thank you again - great analysis!

@isaacabraham
Copy link

At the risk is reopening old discussions (sorry!) I just wanted to add my two cents on the indentation debate - this is (more or less) the styling we follow which does allow for nice, consistent identation and following for the Fable.React style.

The one place this doesn't quite apply to, however, is for non-trivial styling. However, in our experience this accounts for only a minority of elements (maybe 10-20%).

div [ ] [
    Hero.hero [ Hero.IsFullHeight ] [
        Hero.body [ ] [
            Container.container [ ] [
                img [ Src "img/fable-ionide.png"
                      Style [ Display DisplayOptions.Block
                              Width "auto"
                              Margin "auto" ]
                    ]
                br [ ]
                Heading.h3 [ Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ] [
                    str "Fable REPL"
                ]
                Heading.p [ Heading.IsSubtitle
                            Heading.Is5
                            Heading.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Centered) ] ] [
                    str "is only available on desktop"
                ]
            ]
        ]
    ]
]

Also, if you use VS Code and something like Rainbow Brackets, it can really help avoid the bracket overload.

@isaacabraham
Copy link

Something else to consider (maybe I missed this above) is that GiraffeViewEngine uses the Fable.React style i.e. element attr-list children-list, so just something to think about in terms of people transitioning between the two.

@Zaid-Ajaj makes a good point about Femto; is there any reason why this couldn't be implemented by Fable.React-style components, or is there something inherent about Feliz style that Femto is coupled to?

@Zaid-Ajaj
Copy link
Owner

is there any reason why this couldn't be implemented by Fable.React-style components, or is there something inherent about Feliz style that Femto is coupled to?

@isaacabraham

Libraries derived from Fable.React could (and should) add Femto metadata but the Fable.React itself couldn't because it has an incompatible mix of react, react-dom and react-native. @MangelMaxime started some work on splitting packages but it was a lot more work than expected and will probably introduce breaking changes.

What this means for libraries derived from Fable.React is that they have to include not just npm metadata of their dependencies but also include compatible versions of react and react-dom because Fable.React doesn't do that for them (unlike Feliz which starts with react and react-dom so that Feliz extensions only add what they need). Unfortunately, I have yet to see any of those libraries take the initiative to better the situation even if the solution has been around for quite some time now.

Regarding formatting: Fantomas is not able yet to format either DSLs in a sensible way and requires some work on making list formatting configurable or have some kind of "Feliz-mode" flag to format the React DSL using nice defaults (which we have to come up with)

@l3m
Copy link
Contributor

l3m commented Apr 8, 2020

@Zaid-Ajaj The issue with React npm dependencies is also more complex as it should be possible to use preact instead of react, without breaking Femto.

@MangelMaxime
Copy link
Contributor Author

@l3m Indeed, but in this case we could make an addition to Femto to support this case. It remembers me of something so perhaps we have an issue about that in Femto repo (sorry don't have the time to check right now ^^)

@Zaid-Ajaj
Copy link
Owner

@l3m Preact can be used in place of React regardless of Femto npm metadata because you only need to configure module import aliases as per the guidelines in Switching to Preact when you have an existing React application.

Building a pure Preact binding (100% non-React code) is not the goal of Feliz and should probably be implemented as a standalone library (Feliz.Preact that doesn't depend on Feliz) from which Preact-only bindings will be extended and used in case you want to stay away from preact/compat

@l3m
Copy link
Contributor

l3m commented Apr 8, 2020

@Zaid-Ajaj I'm using preact right now in my Elmish app and it works well. I was just wary about Femto modifying packages.json if it sees a nuget with React deps and I wanted to highlight that. If that's not a problem, all the better.

Also, preact/compat is no longer needed with the latest preact, it's now in core.

@fc1943s
Copy link

fc1943s commented Apr 8, 2020

At the risk is reopening old discussions (sorry!) I just wanted to add my two cents on the indentation debate - this is (more or less) the styling we follow which does allow for nice, consistent identation and following for the Fable.React style.

I'm indenting code in a very similar way from Isaac's, and its pretty readable.
I know its hard to compare with a single list way of doing things, but the indenting of the original post could reflect something similar to his code to better compare.

I suppose that with F# 5 and the ability to "open static classes" Feliz will be able to support both Html.div and div syntax depending on if you open or not the Html class.

That would be super cool. Would it be possible to make the Html class static so it could be opened?

@Zaid-Ajaj
Copy link
Owner

@fc1943s Html, prop and style are all static classes. However, I don't recommend opening them when the feature becomes available in F# 5 because the whole point is having a grouping and an entry point of discovery to the available functions within that static class.

@MaxWilsonMS
Copy link

How would opening the static class hinder discovery? You could still type Feliz.Html.<Ctrl-.> when you want to discover what's available.

@MangelMaxime
Copy link
Contributor Author

@MaxWilsonMS That required additional steps to output the "final" code.

And you would write Feliz.Html.div and then erase Feliz.Html. part which seems strange.

What's good is that people can choose to open the static class or not depending on their preferences. :)

@drk-mtr
Copy link

drk-mtr commented Apr 13, 2020

Not sure how useful this will be since I'm echoing what has been said above, but experiences as a complete beginner:

  • I prefer the Feliz approach for indentation. I've tried approaches similar to what Isaac has suggested, but because indents aren't all 2s/4s "I sometimes spend time thinking about it". With Feliz, I spend no time thinking about it at all.

  • If I was making a custom element, I'd be doing it the Fable.React way for now. I took a look at building things "the Feliz way" and ran away instantly (I'll revisit at some point!).

  • I would find it really useful if documentation would more regularly prioritise the "this is how to make a custom property" aspect. I think it's fair to accept that with just a few authors, these libraries are often going to struggle to offer an API surface for everything possible in CSS (for example), so if a beginner gets stuck when they can't set "fontSize" in "em", or can't find "grid-template-rows", or have no idea that they're expected to supply some "Screen.All" argument - it'd good if they knew how to work around it.

  • I think some "Quality of Life" improvements could be made that start to chip away at some potential concerns... for example when I look at long vertical lists of style props in Feliz I start wondering whether there could be a style.paddingHorizontal and style.paddingVertical. I realise I could make my own helpers, and this isn't the place to make feature requests though, and that there may be a desire to avoid introducing things that aren't standard :)

  • I like the idea of opening the Html static class in the new F#. Critically - my colleagues would be able to make sense of it when they look over my shoulder - and I think getting more people involved is important. To play devil's advocate: div doesn't take long to type and if I'm needing to discover it then this is the least of the challenges I'm going to hit when using a setup that is doing magic while hiding Javascript, React, CSS etc. etc. from me. Generally though, the discoverability of the API is great.

  • Particularly for transitions / animations on styles, the Feliz way just makes so much sense - really nice to work with.

  • More generally and probably tangential: The challenge that I consistently hit is around understanding where Elmish world ends and React world starts. This is not really a concern of either library - but since I don't have the foggiest clue about anything React, the fact I could get a Function Component from Fable.React or Feliz confuses me. Of course, learning this is my responsibility - but I thought it was worth mentioning. I'm currently mashing together Elmish and React approaches and I believe this results in all components re-rendering on update - but I don't know the "right" way to do things to avoid this because examples of fully built apps like this are hard to find. Sometimes I get the impression I should learn to make production-grade React apps in Javascript first, then graduate to Fable/Elmish - but that feels like a fairly significant barrier to entry. Disclaimer: I haven't read the Feliz documentation extensively, and it's really good - so this is probably on me!

Thanks to all for sharing these libraries!

@Shmew
Copy link
Collaborator

Shmew commented Apr 13, 2020

Thanks for your input @drk-mtr!

If I was making a custom element, I'd be doing it the Fable.React way for now. I took a look at building things "the Feliz way" and ran away instantly (I'll revisit at some point!).

Yeah, it can appear pretty daunting at first, but it's pretty simple once you get used to it. That being said it is a fair bit of initial cost for the developer when writing libraries. I find this a pretty acceptable trade-off since the vast majority of users aren't going to be writing libraries. Luckily the work itself is pretty simple, and some copy + paste can speed up a lot of the work (or like some of us, writing generators to build the code for us).

I would find it really useful if documentation would more regularly prioritise the "this is how to make a custom property" aspect. I think it's fair to accept that with just a few authors, these libraries are often going to struggle to offer an API surface for everything possible in CSS (for example), so if a beginner gets stuck when they can't set "fontSize" in "em", or can't find "grid-template-rows", or have no idea that they're expected to supply some "Screen.All" argument - it'd good if they knew how to work around it.

Just in case you aren't aware (I couldn't quite tell from this context):

There is a custom method on both the prop and style types.

Everything that accepts ICssUnit can be set like: style.fontSize (length.em 1)

Updating the docs should be easy enough.

I think some "Quality of Life" improvements could be made that start to chip away at some potential concerns... for example when I look at long vertical lists of style props in Feliz I start wondering whether there could be a style.paddingHorizontal and style.paddingVertical. I realise I could make my own helpers, and this isn't the place to make feature requests though, and that there may be a desire to avoid introducing things that aren't standard :)

Anything that helps make the user experience easier is definitely welcome in my book. I'd love to see an issue/pr for any of these things you've noticed!

More generally and probably tangential: The challenge that I consistently hit is around understanding where Elmish world ends and React world starts. This is not really a concern of either library - but since I don't have the foggiest clue about anything React, the fact I could get a Function Component from Fable.React or Feliz confuses me. Of course, learning this is my responsibility - but I thought it was worth mentioning. I'm currently mashing together Elmish and React approaches and I believe this results in all components re-rendering on update - but I don't know the "right" way to do things to avoid this because examples of fully built apps like this are hard to find. Sometimes I get the impression I should learn to make production-grade React apps in Javascript first, then graduate to Fable/Elmish - but that feels like a fairly significant barrier to entry. Disclaimer: I haven't read the Feliz documentation extensively, and it's really good - so this is probably on me!

My experience in the Fable world (pre-Feliz) was I wrote everything as just normal functions for the most part, (like one component for each .fs). The result was I lost a lot of the benefits that the React system provides. Since starting with Feliz I've had to really dive into more on how React works (a good thing imo). Since I've gotten used to it I don't really even give it much conscious thought anymore. Every single function I write that does rendering is a function component.

If you really want to learn how to write React idiomatically I think it's really as simple as restricting yourself to not using Elmish, and only using function components/hooks in your demo app. You can quite easily follow along with anything written in JS out there as the API is almost exactly the same.

Having a quality open-source application written in Feliz is definitely something that would be beneficial, I created an issue for it a while back #67. The closest thing right now is probably the docs app itself, or @cmeeren's Electron Demo.

@jkone27
Copy link

jkone27 commented Oct 30, 2023

still very relevant discussion also in 2023, even more so with HTMX and the ease of Feliz.ViewEngine, also for pure HTML templating in backend, together with also the other templating approach using string templates withhtml prefix or jsx prefix, but i still think the F# list Feliz approach is just amazing being F# code itself.

i think unification in this area between Fable and Feliz view engine approaches as a single package for both backend and fronted could make sense?

@laurentpayot
Copy link

@l3m

For me personally, even though the double list formatting is a pain sometimes, I feel that looking at Fable.React code I get to see the structure of the underlying html better. It's div [.. , not Html.div [.... (etc). It's not a big thing, but my brain seems to have an easier parsing Fable.React.

I’m a little late to the party but I agree that the Html. and prop. prefixes everywhere add some unnecessary noise to the code.
You can get rid of the Html. prefix by using open type Html. It is unsafe to do the same with prop because it would replace core F# functions like async. I simply use type P = prop to shorten the prefix.

IMHO, having to read the children prop to see where the children are is more tiring to the eye than when you have two well separated lists. So I use __ as an easily recognizable shortcut for prop.children:

// signature needed to tell the compiler what implementation of prop.children to use
let __: _ seq -> _ = prop.children

All together, I get view elements like the following:

let pageLoader: ReactElement =
    div [
        P.className "center-xy max-xy fixed top left"
        __ [
            progress [ P.className "delayed circle large" ]
        ]
    ]

Just my two cents…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests