Professional Documents
Culture Documents
GitHub - alfonsogarciacaroFableLit Write Fable Elmish Apps With Lit-Html
GitHub - alfonsogarciacaroFableLit Write Fable Elmish Apps With Lit-Html
Fable.Lit
Fable.Lit is a collection of tools to help you write Fable apps by embedding HTML code
into your F# code with the power of lit-html. Thanks to this, you can use HTML from a
designer or a component library right away, without any kind of conversion to F#. lit-html
only weighs 3KB minified and gzipped so it's very cheap to integrate in your existing app
(see below for React integration). And if you're using VS Code and install the Highlight
HTML/SQL templates in F# extension the integration will be even smoother:
There's an example in the sample directory, but you can find here a more detailed
tutorial by Angel Munoz.
Requirements
Fable.Lit packages require lit-html 2 from npm and fable 3.3 dotnet tool which are both,
at the time of writing, in prerelease state.
Then, in the directory of your .fsproj, install the packages you need (see below for details),
which are also in prelease. Note the package ids are prefixed by Fable. but not the
actual namespaces.
Fable.Lit contains bindings and extra helpers for lit-html. Please read lit-html
documentation to learn how Lit templates work.
When you open the Lit namespace, you will have access to:
html and svg helpers, which convert F# interpolated strings into lit-html
templates
LitHtml static class containing raw bindings for lit-html (normally you don't need
to use this)
1/7
Lit static class containing wrappers for lit-html in a more F# idiomatic fashion
E.g. lit-html repeat directive becomes Lit.mapUnique to map a sequence of items into
Lit.TemplateResult and assign each a unique id. This is important to identify the
items when the list is going to be sorted or filtered. For static lists passing the sequence
directly just works.
HookComponent
Fable.Lit includes the HookComponent attribute. When you decorate a view function
with it, this lets you use hooks in a similar way as ReactComponent attribute does. Hook
support is included in Fable.Lit's F# code and doesn't require any extra JS dependency
besides lit-html.
[<HookComponent>]
let NameInput() =
// Lit.Hook API is currently evolving, we try to emulate React's API but there
may be some differences
let value, setValue = Hook.useState "World"
let inputRef = Hook.useRef<HTMLInputElement>()
html $"""
<div class="content">
<p>Hello {value}!</p>
<input
value={value}
{Lit.refValue inputRef}
@focus={fun _ ->
inputRef.value |> Option.iter (fun el -> el.select())}
@keyup={fun (ev: Event) ->
ev.target.Value |> setValue}>
</div>
"""
Note that hook components are just a way to keep state between renders and are not web
components. We plan to add bindings to define web components with lit in the near future.
Also check Fable.Haunted by Angel Munoz to define actual web components with React-
style hooks.
Hook.useElmish
Thanks to the great work by Cody Johnson with Feliz.UsElmish, Fable.Lit
HookComponents also include useElmish hook to manage the internal state of your
components using the model-view-update architecture.
2/7
open Elmish
open Lit
type Model = ..
type Msg = ..
let init() = ..
let update msg model = ..
let view model dispatch = ..
[<HookComponent>]
let Clock(): TemplateResult =
let model, dispatch = Hook.useElmish(init, update)
view model dispatch
Lit.React
Fable.Lit.React package contains helpers to integrate lit-html with React in both
directions: either by rendering a React component with an HTML template or by
embedding a React component in an HTML template. This makes it possible to add raw
HTML to your apps whenever you need it, no matter you're using Fable.React bindings or
Zaid Ajaj's Feliz API.
If you're comfortable with JSX and Typescript/JS, it's also easy to invoke them from Feliz if
that suits your needs better.
Use React.lit_html (or svg) to include the string template directly. Or transform an
already-compiled template with React.ofLit: Lit.TemplateResult ->
ReactElement . These helpers use hooks so they must be called directly in the root of a
React component.
3/7
[<ReactComponent>]
let Clock () =
let time, setTime = React.useState DateTime.Now
React.useEffectOnce(fun () ->
let id = JS.setInterval (fun _ -> DateTime.Now |> setTime) 1000
React.createDisposable(fun () ->
JS.clearInterval id))
React.lit_html $"""
<svg viewBox="0 0 100 100"
width="350px">
<circle
cx="50"
cy="50"
r="45"
fill="#0B79CE"></circle>
{clockHand time.AsHour}
{clockHand time.AsMinute}
{clockHand time.AsSecond}
<circle
cx="50"
cy="50"
r="3"
fill="#0B79CE"
stroke="#023963"
stroke-width="1">
</circle>
</svg>
"""
4/7
module ReactLib =
open Fable.React
open Fable.React.Props
[<ReactComponent>]
let MyComponent showClock =
let state = Hooks.useState 0
div [ Class "card" ] [
div [ Class "card-content" ] [
div [ Class "content" ] [
p [] [str $"""I'm a React component. Clock is {if showClock
then "visible" else "hidden"}"""]
button [
Class "button"
OnClick (fun _ -> state.update(state.current + 1))
] [ str $"""Clicked {state.current} time{if state.current = 1
then "" else "s"}!"""]
]
]
]
open Lit
let ReactLitComponent =
React.toLit ReactLib.MyComponent
// Now you can embed the React component into your lit-html template
let view model dispatch =
html $"""
<div class="vertical-container">
{ReactLitComponent model.ShowClock}
{if model.ShowClock then Clock.Clock() else Lit.nothing}
</div>
"""
Lit.Elmish
Fable.Lit.Elmish allows you to write a frontend app using the popular Elmish library by
Eugene Tolmachev with a view function returning Lit.TemplateResult . The package
also includes support for Hot Module Replacement out-of-the-box thanks to Maxime
Mangel original work with Elmish.HMR.
5/7
open Elmish
open Lit
type Model = ..
type Msg = ..
let init() = ..
let update msg model = ..
let view model dispatch = ..
open Lit.Elmish
open Lit.Elmish.HMR
Lit.Feliz
Fable.Lit.Feliz package makes it possible to build lit-html templates in a type-safe manner
using Feliz.Engine (check here the differences between original Feliz and Feliz.Engine
APIs).
The only thing you need to take into account is templates are built once and then cached
(this is necessary because of the way lit-html 2 templates work), so you cannot change the
structure of a template dynamically with conditions. This doesn't mean that templates
need to be static forever, but you need to "compile" (that is, convert with Feliz.toLit:
Lit.Feliz.Node -> Lit.TemplateResult ) any nested structure that is bound to
change. The exception to this are CSS styles and single text nodes, which are considered
values and not structure, so they can change dynamically.
// Do this instead
Feliz.ofLit <|
if model.ShowClock then Lit.ofText "Hide clock"
else Feliz.toLit <| Html.strong "Show clock"
6/7
This is actually the same you would do with HTML templates, as you need to put
conditions in a hole of the interpolated strings and declare the nested template externally.
html $"""
<button class="button"
@click={fun _ -> ToggleCock |> dispatch}>
{if model.ShowClock then Lit.ofText "Hide Clock" else strong "Show
Clock"}
</button>
"""
Even if you prefer to use HTML templates, Lit.Feliz contains a useful helper to declare
inline styles with a nicely typed and self-discoverable API:
module Styles =
let verticalContainer = [
Css.marginLeft(rem 2)
Css.displayFlex
Css.justifyContentCenter
Css.alignItemsCenter
Css.flexDirectionColumn
]
7/7