Recently we spent some time playing around with Oxpecker.Solid. Coming from Feliz, the experience was equally familiar and novel. In this blog post, I give a quick overview of the differences and similarities.
Similarities
The comfort of F# and Fable
Oxpecker.Solid runs on top of Fable. This means that the way projects are compiled and run are exactly the same. This gives a comfortable starting ground when switching. We also were able to quickly get going with other browser technologies, such as Tailwind.
Writing DOM in DSL
Both frameworks make use of their own DSL for creating DOM elements. This means that we can make use of the full power of typed F#. On the other hand, it also means that code snippets found online need a bit of translation before being usable. This became painfully evident for me when using DaisyUI; normally, I'd use Feliz.DaisyUI, but since there's no wrapper for Daisy in Oxpecker.Solid, I had to manually build elements without the ease of being able to copy from the Daisy docs.
Differences
Underlying framework
Oxpecker.Solid, as the name suggests, builds on Solid.js, whereas Feliz builds on React. Both are reactive frameworks, meaning that you define UIs using components with attributes that can change, and the UI refreshes accordingly.
Of these two, React is definitely the most established, and therefore has more compatible modules, documentation and community support.
Solid, on the other hand, seems to have a slightly stronger story on how the mechanisms work. React can be quite aggressive in how it updates elements when changes occur. By using fine-grained reactivity with signals (value-returning functions) instead of virtual DOM diffing, Solid can be more precise in how it updates things. From a developer's experience, dealing with these value-returning functions can feel a bit more clunky; I ended up evaluting signals too early, leading to unexpected behavior.. On the other hand, it's a more effective way of making granular changes, making Solid.js faster.
DSL
The Feliz DSL is very simple. You either provide a list of children, or a list of attributes (one of which is 'children') to an HTML element. Because it's a list, you can use whatever list comprehension you like:
Html.ul [
prop.class "font-bold"
prop.children [
for item in listItems do
Html.li item
]
]
The Oxpecker DSL is quite different; it uses constructors with optional parameters (something we don't see a lot in F#), in combination with computational expressions. Also note the Solid.js specific approach to iteration:
ul ( class' = "font-bold" ) {
For(each = items) {
yield fun text _ -> li () {text}
}
}
Both have their advantages; for Oxpecker, it's very clear what the attributes and children are, and in that way it more closely represents XML. On the other hand, Feliz makes it very easy to programmatically add children or parameters using all the well-known tools for mutating lists.
Output
The output generated by Feliz and Oxpecker is quite different. Oxpecker creates nicely formatted .jsx files; this makes analyzing the output really easy. The output generated by Feliz is a lot more arcane, and does not look as similar to code written by hand.
Things we missed
Elmish
As avid fans and maintainers of SAFE stack, we should mention that there's no implementation of Elmish for Oxpecker.Solid yet. Not having the Elmish loop as the core of an application took some getting used to for us. It was a refreshing experience; perhaps we're a bit too hooked on Elmish, and would benefit from using simpler hooks more often when building components.
Conclusion
Oxpecker.Solid is a promising alternative to Feliz for those looking for a Solid.js-based solution. While code does not translate 1:1 between the two, most of the knowledge you gained working with Feliz will be transferable. We're very excited to see how Oxpecker.Solid and Oxpecker will evolve. In a later blog post, we'll compare backend technologies Oxpecker and Giraffe.