Skip to content

Commit 559daaf

Browse files
Fable.Solid blog post
1 parent 22d6fce commit 559daaf

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

docs/blog/2022/2022-10-12-react-jsx.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@ let QrCode() =
9696

9797
You can check [this example](https://github.com/alfonsogarciacaro/fable-react-sample/tree/6cdfa0acda587d4b051c88234d790b4dfcdd9276) to quickly test JSX integration. Just clone the repository and run `npm install && npm start` to launch a development server, and try editing the code in TodoMVC.fs to see the web contents updated on the fly. Please give it a try and let us know what you think!
9898

99-
In a future post, we'll discuss JSX with Fable [SolidJS](https://www.solidjs.com/) apps. Thanks for reading so far and happy coding!
99+
In a [future post](2022-10-18-fable-solid.html), we'll discuss JSX with Fable [SolidJS](https://www.solidjs.com/) apps. Thanks for reading so far and happy coding!
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
layout: fable-blog-page
3+
title: "Fable + Solid: React, the good parts with a performance boost"
4+
author: Alfonso García-Caro
5+
date: 2022-10-18
6+
author_link: https://twitter.com/alfonsogcnunez
7+
author_image: https://github.com/alfonsogarciacaro.png
8+
# external_link:
9+
abstract: |
10+
SolidJS is a tool/library that allows you to write declarative UIs as with React but avoiding most of its pitfalls, and with better performance.
11+
---
12+
13+
When [announcing Fable 4](2022-06-06-Snake_Island_alpha.html) we explained how the new JSX compilation target made Fable compatible with SolidJS. But what is SolidJS? The best way to answer this question is to [check their website](https://www.solidjs.com/) but in short, it's a tool/library that allows you to write React-like apps, while taking advantage of the JSX compilation step to analyze the dependencies in your code and transform your declarative UI into imperative statements that make localized DOM updates, instead of using a Virtual DOM and calculate the diffing at runtime as React does.
14+
15+
This approach was popularized by [Svelte](https://svelte.dev/) in the JS world, but it was also pioneered in F# by [FSharp.Adaptive](https://fsprojects.github.io/FSharp.Data.Adaptive/) and [Sutil](https://sutil.dev/). Some of the advantages are:
16+
17+
- Better performance as the diffing is already done at compile time
18+
- Smaller bundle sizes because the framework doesn't need a big runtime
19+
- Components are "just functions" instead of instances in disguise
20+
21+
The third point has some implications for performance, but the most important consequence is the code within the component will behave as you expect from a regular function. I think this is important for F# developers, as the community has sometimes struggled to understand (and explain) well the difference between functional components and "just" functions. With Solid, the code is only executed when the function is called during the program flow. Consider the following example:
22+
23+
```fsharp
24+
open Fable.Core
25+
26+
[<JSX.Component>]
27+
let Counter() =
28+
printfn "Evaluating function..."
29+
let count, setCount = Solid.createSignal (0)
30+
31+
JSX.html $"""
32+
<>
33+
<p>Count is {let _ = printfn "Evaluating expression..." in count()}</p>
34+
<button class="button" onclick={fun _ -> count () + 1 |> setCount}>
35+
Click me!
36+
</button>
37+
</>
38+
"""
39+
```
40+
41+
The message appearing on the function root, "Evaluating function", is only printed once, while the local message, "Evaluating expression", is printed every time `count` gets updated. This means Solid has detected that, when `count` changes, it only needs to update the re-evaluate the expression in that particular JSX "hole", and it can leave the rest of the component untouched. And Solid is capable of doing this not only with the signal primitives, but also with props coming from a parent component or even an Elmish model!
42+
43+
> To be fair, there is still some magic involved, as you need to understand the expressions containing a reactive value will be re-evaluated every time the value changes.
44+
45+
The UI in Solid apps looks very much like React ones, but you can choose between camelCase or "proper" attribute names, like `class` or `autofocus`. Another important difference is when declaring a **dynamic list or conditional elements:** in these cases Solid requires you to be explicit with [For](https://www.solidjs.com/tutorial/flow_for) and [Show](https://www.solidjs.com/tutorial/flow_show)/[Switch](https://www.solidjs.com/tutorial/flow_switch) respectively. For helpers to create reactive values, effects and such, please check [Solid documentation](https://www.solidjs.com/docs/latest/api) and [Fable.Solid](https://github.com/fable-compiler/Fable.Solid/blob/master/src/Fable.Solid/Solid.fs) bindings.
46+
47+
```fsharp
48+
// Note how `Solid.For` is used to display the list of Todos
49+
let model, dispatch = Solid.createElmishStore (init, update)
50+
51+
JSX.jsx $"""
52+
<>
53+
<p class="title">To-Do List</p>
54+
{InputField dispatch}
55+
<ul>{Solid.For(model.Todos, (fun todo _ -> TodoView todo dispatch))}</ul>
56+
</>
57+
"""
58+
```
59+
60+
<br />
61+
62+
Solid is also compatible with [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components). So if you want to use a library like [Shoelace](https://shoelace.style/), you only need to register the components you need, and then invoke them as if they were native HTML elements. The only thing you need to remember is to prefix custom events with `on:`
63+
64+
```fsharp
65+
open Fable.Core
66+
open Fable.Core.JsInterop
67+
68+
[<JSX.Component>]
69+
let ImageComparer() =
70+
// Cherry-pick Shoelace image comparer element, see https://shoelace.style/components/image-comparer
71+
importSideEffects "https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/dist/components/image-comparer/image-comparer.js"
72+
73+
JSX.html $"""
74+
<sl-image-comparer
75+
position={position()}
76+
on:sl-change={fun (ev: Event) -> printfn "New position: %i" ev.target?position}>
77+
<img slot="before"
78+
src="https://images.unsplash.com/photo-1520903074185-8eca362b3dce"
79+
alt="A person sitting on bricks wearing untied boots."></img>
80+
<img slot="after"
81+
src="https://images.unsplash.com/photo-1520640023173-50a135e35804"
82+
alt="A person sitting on a yellow curb tying shoelaces on a boot."></img>
83+
</sl-image-comparer>
84+
"""
85+
```
86+
87+
> Latest Fable.Core provides `JSX.html` helper. This is an alias of `JSX.jsx` but it's useful if you're using the [F# Template Highlighting](https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight) VS Code extension and you prefer to identify the embededded language as HTML rather than JSX.
88+
89+
If you want to try Solid but still prefer F# for UIs, [Feliz.JSX.Solid](https://github.com/fable-compiler/Feliz.JSX) provides a Feliz-like API for that. And you can freely combine both approaches:
90+
91+
```fsharp
92+
[<JSX.Component>]
93+
let QrCode() =
94+
importSideEffects "https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/dist/components/qr-code/qr-code.js"
95+
let value, setValue = Solid.createSignal("https://shoelace.style/")
96+
97+
Html.fragment [
98+
Html.input [
99+
Attr.className "input mb-5"
100+
Attr.typeText
101+
Attr.autoFocus true
102+
Attr.value (value())
103+
Ev.onTextChange setValue
104+
]
105+
Html.div [
106+
JSX.html $"""<sl-qr-code value={value()} radius="0.5"></sl-qr-code>"""
107+
]
108+
]
109+
```
110+
111+
<br />
112+
113+
Likewise, if you want to try Solid but prefer to use Elmish to manage state, Elmish.Solid has you covered. Similarly to [useElmish](2022-10-13-use-elmish.html), `createElmishStore` allows you to use Elmish at the component level, but it takes advantage of [Solid stores](https://www.solidjs.com/tutorial/stores_nested_reactivity) to compare the model snapshots and update only the updated parts. Check how the classic Elmish TodoMVC is rendered with Solid, in [this example](https://github.com/fable-compiler/Fable.Solid/blob/master/src/App/TodoElmish.fs).
114+
115+
> For Solid store diffing to work best, you should use **arrays** (instead of, say, lists or sets) for collections that correspond with on-screen elements.
116+
117+
<hr />
118+
119+
One particular feature of `createElmishStore` is that it can keep state with [Vite hot reload](). I say "particular" because, at the time of writing, there's no equivalent of React's Fast Refresh and Solid primitive signals won't keep state while hot reloading. How is it possible to get better tooling with Fable/F# than with native JS? We will talk about this in the next post.

0 commit comments

Comments
 (0)