Lit.Server.Unofficial
2.21.3
dotnet add package Lit.Server.Unofficial --version 2.21.3
NuGet\Install-Package Lit.Server.Unofficial -Version 2.21.3
<PackageReference Include="Lit.Server.Unofficial" Version="2.21.3" />
<PackageVersion Include="Lit.Server.Unofficial" Version="2.21.3" />
<PackageReference Include="Lit.Server.Unofficial" />
paket add Lit.Server.Unofficial --version 2.21.3
#r "nuget: Lit.Server.Unofficial, 2.21.3"
#:package Lit.Server.Unofficial@2.21.3
#addin nuget:?package=Lit.Server.Unofficial&version=2.21.3
#tool nuget:?package=Lit.Server.Unofficial&version=2.21.3
Lit.Server.Unofficial
Render Fable.Lit templates to HTML on .NET — no Node, no @lit-labs/ssr.
dotnet add package Lit.Server.Unofficial
The same html $"..." view compiles to lit in the browser and to HTML on the server,
because both sides resolve open Lit — one against this package, the other against
Fable.Lit.Unofficial. No conditional compilation, no template written twice.
open Lit
open Lit.Server
let counter, _ = Views.Counter.init ()
// Plain HTML.
Server.render (Views.Counter.view counter ignore)
// HTML carrying lit's hydration markers, so the browser can adopt it
// with Hydrate.adopt or Program.withLitHydrated.
Server.renderHydratable (Views.Counter.view counter ignore)
Server.toNode and Server.toHydratableNode return a node rather than a string, for
composing into HtmlTypeProvider templates
without anything being escaped twice. Server.toShadowRootNode emits
<template shadowrootmode="open"> with styles inside, which the parser attaches as a
shadow root while it reads the page.
Event handlers are dropped on the server — a closure cannot be serialised — and become real listeners the moment lit adopts the markup.
A component's shadow root
A LitElement can arrive rendered as well. Keep what it draws where both sides reach it,
write that into the component's own tag, and switch adoption on in the browser:
// Shared: compiled by both.
module Badge =
let styles = ":host { display: block }"
let view (count: int) (bump: unit -> unit) =
html $"""<button @click={Ev(fun _ -> bump ())}>{count}</button>"""
// Server: what goes inside <my-badge></my-badge> in the page.
Server.toShadowRootNode Badge.styles (Badge.view 0 ignore)
// Browser: once, at startup.
Hydrate.elements ()
[<LitElement("my-badge")>]
let MyBadge () =
LitElement.init (fun config -> config.styles <- [ Lit.unsafeCSS Badge.styles ]) |> ignore
let count, setCount = Hook.useState 0
Badge.view count (fun () -> setCount (count + 1))
The component must render, first time, what the server rendered: the same view from the
same data. Without Hydrate.elements () it draws a second copy beside the server's, and
says so in the console.
Inside a view
toShadowRootNode fills a hole in a page template. A component that sits inside an
island's view, or inside another component's, has a view around it instead, and
Lit.shadowRoot is the same thing where a view can reach it:
// Added to the Badge module above: what the server draws for a badge inside a view.
let drawn (count: int) = Lit.shadowRoot styles (view count ignore)
// A view with a badge in it.
let view model dispatch =
html $"""<section>
<my-badge>{Badge.drawn model.Count}</my-badge>
<button @click={Ev(fun _ -> dispatch Reset)}>reset</button>
</section>"""
On the server it writes the component's shadow root into its tag. In the browser it is
Lit.nothing, because there a component draws its own root. It has to come first inside
the element, which is the element the parser attaches the root to.
The styles and the view are paired once, in the component's own module, rather than in
each view that uses it. Lit.Server does not work out a component's stylesheet, so that
pairing is the one place the two are kept from disagreeing. If they do disagree, a
development build of the component says so in the console.
If the view hands the component a property (.count={model.Count}), the property is not
in the HTML, so the component is marked defer-hydration and waits: hydrating the view
takes the mark off and sets the property in the same pass.
What it refuses
Anything it cannot render the way lit would raises UnsupportedTemplateValue rather than
guessing. Two of those are easy to reach for:
- A
Nodein a hole. A view is the same code the browser runs, and there is noNodethere. For a shadow root inside a view there isLit.shadowRoot; anything else composes the other way round, the template'sNodeinto the page's hole. - A
<template>element in markup lit is going to adopt. lit never looks inside one, so every binding after it would silently never be made. PlainrenderandtoNodewrite it as it stands.
There is a worked example at LitHydrationDemo.
This package has no Fable dependency: it is ordinary .NET, referenced by the server project.
Why "Unofficial"
This is a republished build of Fable.Lit by Alfonso García-Caro Núñez and its contributors, packaged from a fork so that fixes can be used before they land upstream. The toolchain is .NET 10, Fable 5, Fable.Core 4 and lit 3.
Reference this or upstream Fable.Lit, never both: they share the Lit namespace.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Core (>= 10.1.401)
- HtmlTypeProvider (>= 0.9.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.21.3 | 97 | 10/6/2026 |
| 2.21.2 | 87 | 10/6/2026 |
| 2.21.1 | 85 | 10/6/2026 |
| 2.21.0 | 81 | 10/6/2026 |
| 2.20.0 | 85 | 10/6/2026 |
| 2.19.0 | 133 | 8/14/2026 |
| 2.18.0 | 111 | 8/14/2026 |
| 2.17.0 | 123 | 8/14/2026 |
| 2.16.0 | 118 | 8/14/2026 |
| 2.15.0 | 116 | 8/14/2026 |
| 2.14.0 | 119 | 8/13/2026 |
| 2.13.0 | 109 | 8/13/2026 |
| 2.12.0 | 108 | 8/13/2026 |
| 2.11.0 | 112 | 8/13/2026 |
| 2.10.0 | 110 | 8/13/2026 |
| 2.9.0 | 109 | 8/13/2026 |
| 2.8.0 | 114 | 8/13/2026 |
| 2.7.1 | 115 | 8/13/2026 |
| 2.7.0 | 110 | 8/13/2026 |
| 2.6.0 | 114 | 8/12/2026 |
Unofficial fork of Fable.Lit. Toolchain moved to .NET 10, Fable 5, Fable.Core 4 and lit 3; event handlers no longer raise FS3884. See the repository for what differs from upstream.