Feliz.ViewEngine lets you render Feliz DSL to plain HTML (or XML). Use with e.g Giraffe for handling Server Side Rendering (SSR), returning HTML or XML. You can use it for e.g generating HTML emails or any other use-case where you need to generate HTML output.
Feliz.ViewEngine have no dependencies, is Fable compatible, and can thus be used with both servers (e.g Node.js) or clients.
Feliz.ViewEngine is available as a NuGet package. To install:
Using Package Manager:
Install-Package Feliz.ViewEngine
Using .NET CLI:
dotnet add package Feliz.ViewEngine
open Feliz.ViewEngine
let html =
Html.h1 [
prop.style [ style.fontSize(100); style.color("#137373") ]
prop.text "Hello, world!"
]
|> Render.htmlView
printfn "Output: %s" html
// Will output "<h1 style=\"font-size:100px;color:#137373\">Hello, world!</h1>"
Giraffe example at https://github.com/dbrattli/Feliz.ViewEngine/blob/master/examples/giraffe/Program.fs
Feliz.ViewEngine re-implements Feliz DSL for server-side so you will need to choose Feliz for client side rendering and Feliz.ViewEngine for server side rendering:
#if FABLE_COMPILER
open Feliz
#else
open Feliz.ViewEngine
#endif
let view = ...
The following API is available for converting a ReactElement
view into a string that you can return from e.g a Giraffe
HTTP handler.
type Render
/// Create HTML document view with <!DOCTYPE html>
static member htmlDocument: document: ReactElement -> string
/// Create HTML view
static member htmlView: node: ReactElement -> string (+ 1 overloads)
/// Create XML document view with <?xml version="1.0" encoding="utf-8"?>
static member xmlDocument: document: ReactElement -> string
/// Create XML view
static member xmlView: node: ReactElement -> string (+ 1 overloads)
Feliz has extensive documentation at https://zaid-ajaj.github.io/Feliz with live examples along side code samples, check them out and if you have any question, let us know!
Feliz.ViewEngine (ReactElement
) is not compatible with GiraffeViewEngine (XmlNode
) so you cannot mix the two as you
can with Feliz and Fable.React. Thus when you convert your existing server side rendering code, then all the elements
must be converted to Feliz.
Projects and examples using Feliz.ViewEngine:
To port an existing Feliz
library to Feliz.ViewEngine
you basically need to reimplement everything from the library
you want to port. However this is usually not a lot of work since you can reuse most of the files from the existing
library, and you can do the work incrementally and add support for more elements and properties as needed.
Start with the file that generates the HTML elements, comment out the whole file using (* ... *)
and start enabling
element by element. Then port properties, styles, colors, etc.
The Feliz.ViewEngine
types are different from ReactElement
:
type IReactProperty =
| KeyValue of string * obj
| Children of ReactElement list
| Text of string
and ReactElement =
| Element of string * IReactProperty list
| VoidElement of string * IReactProperty list
| TextElement of string
However you usually don't have to care about the difference since the Interop
interface is very similar:
module Interop =
/// Output a string where the content has been HTML encoded.
val mkText: content : 'a -> IReactProperty
val mkChildren: props: #seq<ReactElement> -> IReactProperty
val reactElementWithChildren: name: string -> children: #seq<ReactElement> -> ReactElement
val reactElementWithChild: name: string -> child: 'a -> ReactElement
val createElement: name: string -> props: IReactProperty list -> ReactElement
val createVoidElement: name: string -> props: IReactProperty list -> ReactElement
val createTextElement: content : string -> ReactElement
val createRawTextElement: content : string -> ReactElement
let mkAttr: key: string -> value: obj -> IReactProperty
val mkStyle: key: string -> value: obj -> IStyleAttribute
Using the Interop
module, many elements is exactly the same for Feliz
and Feliz.ViewEngine
. E.g Feliz
code such
as:
module Html =
static member inline div xs = Interop.createElement "div" xs
For Feliz.ViewEngine
it will be exactly the same:
module Html =
static member inline div xs = Interop.createElement "div" xs
However other elements may require some work. E.g all elements that use unboxing such as:
static member inline none : ReactElement = unbox null
static member inline text (value: int) : ReactElement = unbox value
For Feliz.ViewEngine
this needs to be rewritten as:
static member inline none : ReactElement = Interop.createTextElement ""
static member inline text (value: int) : ReactElement = Interop.createTextElement (value.ToString ())
Properties may also require some work, e.g:
[<Erase>]
type prop =
static member inline dangerouslySetInnerHTML (content: string) = Interop.mkAttr "dangerouslySetInnerHTML" (createObj [ "__html" ==> content ])
For Feliz.ViewEngine
this needs to be rewritten as:
type prop =
static member inline dangerouslySetInnerHTML (content: string) = Interop.mkChildren [ Interop.createRawTextElement content ]
As you go along, always remember that Feliz.ViewEngine
and SSR is about generating HTML that will become text. You
just need to make sure that the elements and properties you add generate the expected text output when rendered. Thus
you can add unit-tests to check the output is as expected by calling Render.htmlView
:
[<Fact>]
let ``h1 element with text and style property with css unit is Ok``() =
// Arrange / Act
let result =
Html.h1 [
prop.style [ style.fontSize(length.em(100)) ]
prop.text "examples"
]
|> Render.htmlView
// Assert
test <@ result = "<h1 style=\"font-size:100em\">examples</h1>" @>
This work is dual-licensed under Apache 2.0 and MIT. You can choose between one of them if you use this work.
SPDX-License-Identifier: Apache-2.0 OR MIT
Yes, Feliz.ViewEngine duplicates a lot of code and violates the DRY principle. This is currently by design.