{"id":16715415,"url":"https://github.com/dbrattli/feliz.viewengine","last_synced_at":"2025-03-21T20:33:56.462Z","repository":{"id":151378556,"uuid":"256710745","full_name":"dbrattli/Feliz.ViewEngine","owner":"dbrattli","description":"Feliz DSL and engine for HTML generation and server side rendering (SSR)","archived":false,"fork":false,"pushed_at":"2020-10-10T14:04:01.000Z","size":336,"stargazers_count":67,"open_issues_count":9,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-18T05:08:09.156Z","etag":null,"topics":["fable","feliz","feliz-syntax","fsharp","giraffe","server-side-rendering","ssr"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dbrattli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-18T09:06:51.000Z","updated_at":"2024-08-01T16:43:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"a1bdf343-69ac-4866-b91e-eb8a35af366c","html_url":"https://github.com/dbrattli/Feliz.ViewEngine","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FFeliz.ViewEngine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FFeliz.ViewEngine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FFeliz.ViewEngine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FFeliz.ViewEngine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dbrattli","download_url":"https://codeload.github.com/dbrattli/Feliz.ViewEngine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244866471,"owners_count":20523520,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fable","feliz","feliz-syntax","fsharp","giraffe","server-side-rendering","ssr"],"created_at":"2024-10-12T21:09:18.474Z","updated_at":"2025-03-21T20:33:56.446Z","avatar_url":"https://github.com/dbrattli.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Feliz.ViewEngine\n\n![Build and Test](https://github.com/dbrattli/Feliz.ViewEngine/workflows/Build%20and%20Test/badge.svg)\n[![NuGet](https://img.shields.io/nuget/v/Feliz.ViewEngine.svg?maxAge=0\u0026colorB=brightgreen)](https://www.nuget.org/packages/Feliz.ViewEngine)\n\nFeliz.ViewEngine lets you render [Feliz](https://github.com/Zaid-Ajaj/Feliz) DSL to plain HTML (or XML). Use with e.g\nGiraffe for handling Server Side Rendering (SSR), returning HTML or XML. You can use it for e.g generating HTML emails\nor any other use-case where you need to generate HTML output.\n\nFeliz.ViewEngine have no dependencies, is Fable compatible, and can thus be used with both servers (e.g Node.js) or\nclients.\n\n## Installation\n\nFeliz.ViewEngine is available as a [NuGet package](https://www.nuget.org/packages/Feliz.ViewEngine/). To install:\n\nUsing Package Manager:\n```sh\nInstall-Package Feliz.ViewEngine\n```\n\nUsing .NET CLI:\n```sh\ndotnet add package Feliz.ViewEngine\n```\n\n## Getting started\n\n```fs\nopen Feliz.ViewEngine\n\nlet html =\n    Html.h1 [\n        prop.style [ style.fontSize(100); style.color(\"#137373\") ]\n        prop.text \"Hello, world!\"\n    ]\n    |\u003e Render.htmlView\n\nprintfn \"Output: %s\" html\n// Will output \"\u003ch1 style=\\\"font-size:100px;color:#137373\\\"\u003eHello, world!\u003c/h1\u003e\"\n```\n\nGiraffe example at https://github.com/dbrattli/Feliz.ViewEngine/blob/master/examples/giraffe/Program.fs\n\n## Sharing views between client and server\n\nFeliz.ViewEngine re-implements Feliz DSL for server-side so you will need to choose Feliz for client side rendering and\nFeliz.ViewEngine for server side rendering:\n\n```fs\n#if FABLE_COMPILER\nopen Feliz\n#else\nopen Feliz.ViewEngine\n#endif\n\nlet view = ...\n```\n\n## Documentation\n\nThe following API is available for converting a `ReactElement` view into a string that you can return from e.g a Giraffe\nHTTP handler.\n\n```fs\ntype Render\n  /// Create HTML document view with \u003c!DOCTYPE html\u003e\n  static member htmlDocument: document: ReactElement -\u003e string\n  /// Create HTML view\n  static member htmlView: node: ReactElement -\u003e string (+ 1 overloads)\n  /// Create XML document view with \u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n  static member xmlDocument: document: ReactElement -\u003e string\n  /// Create XML view\n  static member xmlView: node: ReactElement -\u003e string (+ 1 overloads)\n```\n\nFeliz has extensive documentation at https://zaid-ajaj.github.io/Feliz with live examples along side code samples, check\nthem out and if you have any question, let us know!\n\n## Extensions\n\n- [Feliz.Bulma.ViewEngine](https://www.nuget.org/packages/Feliz.Bulma.ViewEngine/) - Port of\n  [Feliz.Bulma](https://github.com/Dzoukr/Feliz.Bulma) to Feliz.ViewEngine.\n\n## Common Pitfalls\n\nFeliz.ViewEngine (`ReactElement`) is not compatible with GiraffeViewEngine (`XmlNode`) so you cannot mix the two as you\ncan with Feliz and Fable.React. Thus when you convert your existing server side rendering code, then all the elements\nmust be converted to Feliz.\n\n## Projects and Examples\n\nProjects and examples using Feliz.ViewEngine:\n\n- [Felizia](https://github.com/dbrattli/Felizia) - Uses Feliz.ViewEngine server-side for SSR and Feliz client-side\n- [Giraffe server](https://github.com/dbrattli/Feliz.ViewEngine/tree/master/examples/giraffe) - simple example\n\n## Porting an Existing Feliz Library to Feliz.ViewEngine\n\nTo port an existing `Feliz` library to `Feliz.ViewEngine` you basically need to reimplement everything from the library\nyou want to port. However this is usually not a lot of work since you can reuse most of the files from the existing\nlibrary, and you can do the work incrementally and add support for more elements and properties as needed.\n\nStart with the file that generates the HTML elements, comment out the whole file using `(* ... *)` and start enabling\nelement by element. Then port properties, styles, colors, etc.\n\nThe `Feliz.ViewEngine` types are different from `ReactElement`:\n\n```fs\ntype IReactProperty =\n    | KeyValue of string * obj\n    | Children of ReactElement list\n    | Text of string\n\nand ReactElement =\n    | Element of string * IReactProperty list\n    | VoidElement of string * IReactProperty list\n    | TextElement of string\n```\n\nHowever you usually don't have to care about the difference since the `Interop` interface is very similar:\n\n```fs\nmodule Interop =\n    /// Output a string where the content has been HTML encoded.\n    val mkText: content : 'a -\u003e IReactProperty\n    val mkChildren: props: #seq\u003cReactElement\u003e -\u003e IReactProperty\n    val reactElementWithChildren: name: string -\u003e children: #seq\u003cReactElement\u003e -\u003e ReactElement\n    val reactElementWithChild: name: string -\u003e child: 'a -\u003e ReactElement\n    val createElement: name: string -\u003e props: IReactProperty list -\u003e ReactElement\n    val createVoidElement: name: string -\u003e props: IReactProperty list -\u003e ReactElement\n    val createTextElement: content : string -\u003e ReactElement\n    val createRawTextElement: content : string -\u003e ReactElement\n    let mkAttr: key: string -\u003e value: obj -\u003e IReactProperty\n    val mkStyle: key: string -\u003e value: obj -\u003e IStyleAttribute\n```\n\nUsing the `Interop` module, many elements is exactly the same for `Feliz` and `Feliz.ViewEngine`. E.g `Feliz` code such\nas:\n\n```fs\nmodule Html =\n    static member inline div xs = Interop.createElement \"div\" xs\n```\n\nFor `Feliz.ViewEngine` it will be exactly the same:\n\n```fs\nmodule Html =\n    static member inline div xs = Interop.createElement \"div\" xs\n```\n\nHowever other elements may require some work. E.g all elements that use unboxing such as:\n\n```fs\n    static member inline none : ReactElement = unbox null\n    static member inline text (value: int) : ReactElement = unbox value\n```\n\nFor `Feliz.ViewEngine` this needs to be rewritten as:\n\n```fs\n    static member inline none : ReactElement = Interop.createTextElement \"\"\n    static member inline text (value: int) : ReactElement = Interop.createTextElement (value.ToString ())\n```\n\nProperties may also require some work, e.g:\n\n```fs\n[\u003cErase\u003e]\ntype prop =\n    static member inline dangerouslySetInnerHTML (content: string) = Interop.mkAttr \"dangerouslySetInnerHTML\" (createObj [ \"__html\" ==\u003e content ])\n\n```\n\nFor `Feliz.ViewEngine` this needs to be rewritten as:\n\n```fs\ntype prop =\n    static member inline dangerouslySetInnerHTML (content: string) = Interop.mkChildren [ Interop.createRawTextElement content ]\n\n```\n\nAs you go along, always remember that `Feliz.ViewEngine` and SSR is about generating HTML that will become text. You\njust need to make sure that the elements and properties you add generate the expected text output when rendered. Thus\nyou can add unit-tests to check the output is as expected by calling `Render.htmlView`:\n\n```fs\n[\u003cFact\u003e]\nlet ``h1 element with text and style property with css unit is Ok``() =\n    // Arrange / Act\n    let result =\n        Html.h1 [\n            prop.style [ style.fontSize(length.em(100)) ]\n            prop.text \"examples\"\n        ]\n        |\u003e Render.htmlView\n\n    // Assert\n    test \u003c@ result = \"\u003ch1 style=\\\"font-size:100em\\\"\u003eexamples\u003c/h1\u003e\" @\u003e\n```\n\n\n## License\n\nThis work is dual-licensed under Apache 2.0 and MIT. You can choose between one of them if you use this work.\n\n`SPDX-License-Identifier: Apache-2.0 OR MIT`\n\n## Duplication of Code\n\nYes, Feliz.ViewEngine duplicates a lot of code and violates the [DRY\nprinciple](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). This is currently [by\ndesign](https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbrattli%2Ffeliz.viewengine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbrattli%2Ffeliz.viewengine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbrattli%2Ffeliz.viewengine/lists"}