{"id":19420529,"url":"https://github.com/giraffe-fsharp/giraffe.viewengine","last_synced_at":"2025-04-07T05:14:42.608Z","repository":{"id":39726012,"uuid":"255314961","full_name":"giraffe-fsharp/Giraffe.ViewEngine","owner":"giraffe-fsharp","description":"An F# view engine for Giraffe and other ASP.NET Core web applications.","archived":false,"fork":false,"pushed_at":"2024-09-03T20:56:49.000Z","size":143,"stargazers_count":46,"open_issues_count":6,"forks_count":14,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-09-18T06:27:51.031Z","etag":null,"topics":["framework","fsharp","giraffe","html","mvvm","svg","ui","view-engine","web","xml"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/giraffe-fsharp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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-13T12:07:28.000Z","updated_at":"2024-08-17T12:25:08.000Z","dependencies_parsed_at":"2024-06-18T14:03:44.665Z","dependency_job_id":"9f9b09d5-562a-478c-af13-85431996c906","html_url":"https://github.com/giraffe-fsharp/Giraffe.ViewEngine","commit_stats":{"total_commits":55,"total_committers":8,"mean_commits":6.875,"dds":0.1636363636363637,"last_synced_commit":"467524c5f607619193013a8c5fdc0da4b66c598b"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giraffe-fsharp%2FGiraffe.ViewEngine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giraffe-fsharp%2FGiraffe.ViewEngine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giraffe-fsharp%2FGiraffe.ViewEngine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giraffe-fsharp%2FGiraffe.ViewEngine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/giraffe-fsharp","download_url":"https://codeload.github.com/giraffe-fsharp/Giraffe.ViewEngine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247595335,"owners_count":20963943,"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":["framework","fsharp","giraffe","html","mvvm","svg","ui","view-engine","web","xml"],"created_at":"2024-11-10T13:23:47.663Z","updated_at":"2025-04-07T05:14:42.576Z","avatar_url":"https://github.com/giraffe-fsharp.png","language":"F#","readme":"![Giraffe](https://raw.githubusercontent.com/giraffe-fsharp/Giraffe/master/giraffe.png)\n\n# Giraffe.ViewEngine\n\nAn F# view engine for [Giraffe](https://github.com/giraffe-fsharp/Giraffe) and other ASP.NET Core web applications.\n\n[![NuGet Info](https://buildstats.info/nuget/Giraffe.ViewEngine?includePreReleases=true)](https://www.nuget.org/packages/Giraffe.ViewEngine/)\n\n### Linux, macOS and Windows Build Status\n\n![.NET Core](https://github.com/giraffe-fsharp/Giraffe.ViewEngine/workflows/.NET%20Core/badge.svg?branch=develop)\n\n[![Build history](https://buildstats.info/github/chart/giraffe-fsharp/Giraffe.ViewEngine?branch=develop\u0026includeBuildsFromPullRequest=false)](https://github.com/giraffe-fsharp/Giraffe.ViewEngine/actions?query=branch%3Adevelop++)\n\n## Table of contents\n\n- [About](#about)\n- [Documentation](#documentation)\n    - [Overview](#overview)\n    - [HTML Elements and Attributes](#html-elements-and-attributes)\n    - [Text Content](#text-content)\n    - [Naming Convention](#naming-convention)\n    - [Javascript event handlers](#javascript-event-handlers)\n    - [Custom Elements and Attributes](#custom-elements-and-attributes)\n    - [Rendering Views](#rendering-views)\n        - [Rendering HTML](#rendering-html)\n        - [Rendering XML](#rendering-xml)\n        - [StringBuilder Pools](#stringbuilder-pools)\n    - [Common Patterns](#common-patterns)\n        - [Master Pages](#master-pages)\n        - [Partial Views](#partial-views)\n        - [Model Binding](#model-binding)\n        - [Logical Constructs](#logical-constructs)\n    - [Best Practices](#best-practices)\n- [Samples](#samples)\n- [Attribution to original authors](#attribution-to-original-authors)\n- [Nightly builds and NuGet feed](#nightly-builds-and-nuget-feed)\n- [License](#license)\n\n## About\n\nThe `Giraffe.ViewEngine` is a UI framework which uses traditional F# functions and types to build rich HTML or XML based web views. This means that views built with the `Giraffe.ViewEngine` are automatically compiled into an assembly, don't require any disk I/O to load or render views and users of the `Giraffe.ViewEngine` can utilise the full power of F# to create custom views in every way possible.\n\nOriginally the `Giraffe.ViewEngine` was part of the [Giraffe web framework](https://github.com/giraffe-fsharp/giraffe) but has been completely separated since then and can be used on its own with any other .NET Core web application today.\n\n## Documentation\n\n### Overview\n\nThe `Giraffe.ViewEngine` is an extremely light weight F# DSL (Domain Specific Language) for building HTML.\n\nIt is centered around the following types:\n\n```fsharp\ntype XmlAttribute =\n    | KeyValue of string * string\n    | Boolean  of string\n\ntype XmlElement   = string * XmlAttribute[]    // Name * XML attributes\n\ntype XmlNode =\n    | ParentNode  of XmlElement * XmlNode list // An XML element which contains nested XML elements\n    | VoidElement of XmlElement                // An XML element which cannot contain nested XML (e.g. \u003chr /\u003e or \u003cbr /\u003e)\n    | Text        of string                    // Text content\n```\n\nThe DSL mainly consists of F# functions which will create objects of one of the above defined types. Currently the `Giraffe.ViewEngine` has functions for all of the standard html tags, such as `head`, `body`, `h1`, etc.\n\nPlease see [HTML Elements and Attributes](#html-elements-and-attributes) for further details and to get a better understanding.\n\n### HTML Elements and Attributes\n\nHTML elements and attributes are defined as F# objects:\n\n```fsharp\nlet indexView =\n    html [] [\n        head [] [\n            title [] [ str \"Giraffe Sample\" ]\n        ]\n        body [] [\n            h1 [] [ str \"I |\u003e F#\" ]\n            p [ _class \"some-css-class\"; _id \"someId\" ] [\n                str \"Hello World\"\n            ]\n        ]\n    ]\n```\n\nA HTML element can either be a `ParentNode`, a `VoidElement` or a `Text` element.\n\nFor example the `\u003chtml\u003e` or `\u003cdiv\u003e` tags are typical `ParentNode` elements. They can hold an `XmlAttribute list` and a second `XmlElement list` for their child elements:\n\n```fsharp\nlet someHtml = div [] []\n```\n\nAll `ParentNode` elements accept these two parameters:\n\n```fsharp\nlet someHtml =\n    div [ _id \"someId\"; _class \"css-class\" ] [\n        a [ _href \"https://example.org\" ] [ str \"Some text...\" ]\n    ]\n```\n\nMost HTML tags are `ParentNode` elements, however there is a few HTML tags which cannot hold any child elements, such as `\u003cbr\u003e`, `\u003chr\u003e` or `\u003cmeta\u003e` tags. These are represented as `VoidElement` objects and only accept the `XmlAttribute list` as single parameter:\n\n```fsharp\nlet someHtml =\n    div [] [\n        br []\n        hr [ _class \"css-class-for-hr\" ]\n        p [] [ str \"bla blah\" ]\n    ]\n```\n\nAttributes are further classified into two different cases. First and most commonly there are `KeyValue` attributes:\n\n```fsharp\na [\n    _href \"http://url.com\"\n    _target \"_blank\"\n    _class \"class1\" ] [ str \"Click here\" ]\n```\n\nAs the name suggests, they have a key, such as `class` and a value such as the name of a CSS class.\n\nThe second category of attributes are `Boolean` flags. There are not many but some HTML attributes which do not require any value (e.g. `async` or `defer` in script tags). The presence of such an attribute means that the feature is turned on, otherwise it is turned off:\n\n```fsharp\nscript [ _src \"some.js\"; _async ] []\n```\n\nThere's also a wealth of [accessibility attributes](https://www.w3.org/TR/html-aria/) available under the `Giraffe.ViewEngine.Accessibility` module (needs to be explicitly opened).\n\n### Text Content\n\nNaturally the most frequent content in any HTML document is pure text:\n\n```html\n\u003cdiv\u003e\n    \u003ch1\u003eThis is text content\u003c/h1\u003e\n    \u003cp\u003eThis is even more text content!\u003c/p\u003e\n\u003c/div\u003e\n```\n\nThe `Giraffe.ViewEngine` lets one create pure text content as a `Text` element. A `Text` element can either be generated via the `rawText` or `encodedText` (or the short alias `str`) functions:\n\n```fsharp\nlet someHtml =\n    div [] [\n        p [] [ rawText \"\u003cdiv\u003eHello World\u003c/div\u003e\" ]\n        p [] [ encodedText \"\u003cdiv\u003eHello World\u003c/div\u003e\" ]\n    ]\n```\n\nThe `rawText` function will create an object of type `XmlNode` where the content will be rendered in its original form and the `encodedText`/`str` function will output a string where the content has been HTML encoded.\n\nIn this example the first `p` element will literally output the string as it is (`\u003cdiv\u003eHello World\u003c/div\u003e`) while the second `p` element will output the value as HTML encoded string `\u0026lt;div\u0026gt;Hello World\u0026lt;/div\u0026gt;`.\n\nPlease be aware that the the usage of `rawText` is mainly designed for edge cases where someone would purposefully want to inject HTML (or JavaScript) code into a rendered view. If not used carefully this could potentially lead to serious security vulnerabilities and therefore should be used only when explicitly required.\n\nMost cases and particularly any user provided content should always be output via the `encodedText`/`str` function.\n\n### Naming Convention\n\nThe `Giraffe.ViewEngine` has a naming convention which lets you easily determine the correct function name without having to know anything about the view engine's implementation.\n\nAll HTML tags are defined as `XmlNode` elements under the exact same name as they are named in HTML. For example the `\u003chtml\u003e` tag would be `html [] []`, an `\u003ca\u003e` tag would be `a [] []` and a `\u003cspan\u003e` or `\u003ccanvas\u003e` would be the `span [] []` or `canvas [] []` function.\n\nHTML attributes follow the same naming convention except that attributes have an underscore prepended. For example the `class` attribute would be `_class` and the `src` attribute would be `_src` in the `Giraffe.ViewEngine`.\n\nThe underscore does not only help to distinguish an attribute from an element, but also avoid a naming conflict between tags and attributes of the same name (e.g. `\u003cform\u003e` vs. `\u003cinput form=\"form1\"\u003e`).\n\nIf a HTML attribute has a hyphen in the name (e.g. `accept-charset`) then the equivalent Giraffe attribute would be written in camel case notion after the initial underscore (e.g. `_acceptCharset`).\n\n*Should you find a HTML tag or attribute missing in the `Giraffe.ViewEngine` then you can either [create it yourself](#custom-elements-and-attributes) or send a [pull request on GitHub](https://github.com/giraffe-fsharp/Giraffe.ViewEngine/pulls).*\n\n### Javascript event handlers\n\nIt is possible to add JavaScript event handlers to HTML elements using the `Giraffe.ViewEngine`.  These event handlers (all prefixed with names starting with `_on`, for example `_onclick`, `_onmouseover`) can either execute inline JavaScript code or can invoke functions that are part of the `window` scope.\n\nThis example illustrates how inline JavaScript could be used to log to the console when a button is clicked:\n\n```fsharp\nlet inlineJSButton =\n    button [_id \"inline-js\"\n            _onclick \"console.log(\\\"Hello from the 'inline-js' button!\\\");\"] [str \"Say Hello\" ]\n```\n\nThere are some caveats with this approach, namely that\n* ...it is not very scalable to write JavaScript inline in this manner, and more pressing...\n* ...the `Giraffe.ViewEngine` HTML-encodes the text provided to the `_onX` attributes.\n\nTo get around this, you can write dedicated scripts in your HTML and reference the functions from your event handlers:\n\n```fsharp\nlet page =\n    div [] [\n        script [_type \"application/javascript\"] [\n            rawText \"\"\"\n            window.greet = function () {\n                console.log(\"ping from the greet method\");\n            }\n            \"\"\"\n        ]\n        button [_id \"script-tag-js\"\n                _onclick \"greet();\"] [str \"Say Hello\"]\n    ]\n```\n\nHere it's important to note that we've included the text of our script using the `rawText` tag.  This ensures that our text is not encoded by the `Giraffe.ViewEngine` so that it remains as it was written.\n\nHowever, writing large quantities of JavaScript in this manner can be difficult, because you don't have access to the large ecosystem of javascript editor tooling.  In this case you should write your functions in another script and use a `script` tag element to reference your script, then add the desired function to your HTML element's event handler.\n\nSay you had a JavaScript file named `greet.js` and had configured Giraffe to serve that script from the WebRoot. Let us also say that the content of that script was:\n\n```javascript\nfunction greet() {\n    console.log(\"Hello from the greet function of greet.js!\");\n}\n```\n\nThen, you could reference that javascript via a script element, and use `greet` in your event handler like so:\n\n```fsharp\nlet page =\n    html [] [\n        head [] [\n            script [_type \"application/javascript\"\n                    _src \"/greet.js\"] [] // include our `greet.js` function dynamically\n        ]\n        body [] [\n            button [_id \"greet-btn\"\n                    _onclick \"greet()\"] [] // use the `greet()` function from `greet.js` to say hello\n        ]\n    ]\n```\n\nIn this way, you can write `greet.js` with all of your expected tooling, and still hook up the event handlers all in one place in the `Giraffe.ViewEngine`.\n\n### Custom Elements and Attributes\n\nAdding new elements or attributes is normally as simple as a single line of code:\n\n```fsharp\nopen Giraffe.ViewEngine\n\n// If there was a new \u003cfoo\u003e\u003c/foo\u003e HTML element:\nlet foo = tag \"foo\"\n\n// If \u003cfoo\u003e is an element which cannot hold any content then create it as voidTag:\nlet foo = voidTag \"foo\"\n\n// If \u003cfoo\u003e has a new attribute called bar then create a new bar attribute:\nlet _bar = attr \"bar\"\n\n// if the bar attribute is a boolean flag:\nlet _bar = flag \"bar\"\n```\n\nAlternatively you can also create new elements and attributes from inside another element:\n\n```fsharp\nlet someHtml =\n    div [] [\n        tag \"foo\" [ attr \"bar\" \"blah\" ] [\n            voidTag \"otherFoo\" [ flag \"flag1\" ]\n        ]\n    ]\n```\n\n### Rendering Views\n\nRendering views with the `Giraffe.ViewEngine` can be done in several ways. The `RenderView` module exposes three sub modules which can be used to specify the desired output format:\n\n- `RenderView.IntoStringBuilder` implements functions to render a view into a `StringBuilder` object which can be used for further processing.\n- `RenderView.AsString` implements functions to output a view directly as a `string`.\n- `RenderView.AsBytes` implements functions to output a view directly as a `byte array`.\n\nAll three sub modules implement the following public functions:\n\n- `htmlDocument`\n- `htmlNodes`\n- `htmlNode`\n- `xmlNodes`\n- `xmlNode`\n\n#### Rendering HTML\n\nThe `htmlDocument` function takes a single `XmlNode` as input parameter and renders a HTML page with a `DOCTYPE` declaration. This function should be used for rendering a complete HTML document.\n\nThe `htmlNodes` function takes an `XmlNode list` as input parameter and will output a single HTML string containing all the rendered HTML code. The `htmlNode` function renders a single `XmlNode` element into a valid HTML string. Both, the `htmlNodes` and `htmlNode` function are useful for use cases where a HTML snippet needs to be created without a `DOCTYPE` declaration (e.g. email templates, etc.).\n\n#### Rendering XML\n\nViews cannot only be rendered into HTML pages but also into other XML based content such as SVG images or other data objects.\n\nThe `xmlNodes` and `xmlNode` function are identical to `htmlNodes` and `htmlNode`, except that they will render void elements differently:\n\n```fsharp\nlet someTag = voidTag \"foo\"\nlet someContent = someTag []\n\n// Void tag will be rendered to valid HTML: \u003cfoo\u003e\nlet output1 = RenderView.AsString.htmlNode someContent\n\n// Void tag will be rendered to valid XML: \u003cfoo /\u003e\nlet output2 = RenderView.AsString.xmlNode someContent\n```\n\n#### StringBuilder Pools\n\nAll functions from the `RenderView.AsString` and `RenderView.AsBytes` modules are using a thread static `StringBuilderPool` to avoid the creation of large `StringBuilder` objects for each render call and dynamically grow/shrink that pool based on the application's needs. However if the application is running into any memory issues then this performance feature can be disabled by setting `StringBuilderPool.IsEnabled` to false:\n \n```fsharp\nStringBuilderPool.IsEnabled \u003c- false\n```\n\nAdditionally the `RenderView.IntoStringBuilder` module can be used if full control of the `StringBuilder` object is required:\n\n```fsharp\nopen System.Text\nopen Giraffe.ViewEngine\n\nlet someHtml =\n    div [] [\n        tag \"foo\" [ attr \"bar\" \"blah\" ] [\n            voidTag \"otherFoo\" [ flag \"flag1\" ]\n        ]\n    ]\n\n// Create your own StringBuilder, which gives the caller\n// full control of the lifecycle of the object:\nlet sb = new StringBuilder()\n\n// Perform actions on the `sb` object...\nsb.AppendLine \"This is a HTML snippet inside a markdown string:\"\n  .AppendLine \"\"\n  .AppendLine \"```html\" |\u003e ignore\n\n// Using RederView.IntoStringBuilder some HTML content can be written\n// directly into the given StringBuilder object:\nlet sb' = RederView.IntoStringBuilder.htmlNode sb someHtml\n\n// Perform more actions on the `sb` object...\nsb'.AppendLine \"```\" |\u003e ignore\n\nlet markdownOutput = sb'.ToString()\n```\n\n### Common Patterns\n\nThe `Giraffe.ViewEngine` is nothing more but simple F# code dressed up as a DSL which can be used to compose rich HTML content in a structured way. As such it doesn't require any built-in functions to enable common view engine features such as master pages, partial views or model binding. These things can all be accomplished through normal F# coding patterns:\n\n#### Master Pages\n\nCreating a master page is as simple as piping two functions together:\n\n```fsharp\nmodule Views =\n    open Giraffe.ViewEngine\n\n    let master (pageTitle : string) (content: XmlNode list) =\n        html [] [\n            head [] [\n                title [] [ str pageTitle ]\n            ]\n            body [] content\n        ]\n\n    let index =\n        let pageTitle = \"Giraffe Sample\"\n        [\n            h1 [] [ str pageTitle ]\n            p [] [ str \"Hello world!\" ]\n        ] |\u003e master pageTitle\n```\n\n... or even have multiple nested master pages:\n\n```fsharp\nmodule Views =\n    open Giraffe.ViewEngine\n\n    let master1 (pageTitle : string) (content: XmlNode list) =\n        html [] [\n            head [] [\n                title [] [ str pageTitle ]\n            ]\n            body [] content\n        ]\n\n    let master2 (content: XmlNode list) =\n        [\n            main [] content\n            footer [] [\n                p [] [\n                    str \"Copyright ...\"\n                ]\n            ]\n        ]\n\n    let index =\n        let pageTitle = \"Giraffe Sample\"\n        [\n            h1 [] [ str pageTitle ]\n            p [] [ str \"Hello world!\" ]\n        ] |\u003e master2 |\u003e master1 pageTitle\n```\n\n#### Partial Views\n\nPartial views can be codified by calling one function from within another:\n\n```fsharp\nmodule Views =\n    open Giraffe.ViewEngine\n\n    let partial =\n        footer [] [\n            p [] [\n                str \"Copyright...\"\n            ]\n        ]\n\n    let master (pageTitle : string) (content: XmlNode list) =\n        html [] [\n            head [] [\n                title [] [ str pageTitle ]\n            ]\n            body [] content\n            partial\n        ]\n\n    let index =\n        let pageTitle = \"Giraffe Sample\"\n        [\n            h1 [] [ str pageTitle ]\n            p [] [ str \"Hello world!\" ]\n        ] |\u003e master pageTitle\n```\n\n#### Model Binding\n\nA view which accepts a model is basically a function with an additional parameter:\n\n```fsharp\nmodule Views =\n    open Giraffe.ViewEngine\n\n    let partial =\n        footer [] [\n            p [] [\n                str \"Copyright...\"\n            ]\n        ]\n\n    let master (pageTitle : string) (content: XmlNode list) =\n        html [] [\n            head [] [\n                title [] [ str pageTitle ]\n            ]\n            body [] content\n            partial\n        ]\n\n    let index (model : IndexViewModel) =\n        [\n            h1 [] [ str model.PageTitle ]\n            p [] [ str model.WelcomeText ]\n        ] |\u003e master model.PageTitle\n```\n\n#### Logical Constructs\n\nThings like if statements, loops and other F# language constructs just work as expected:\n\n```fsharp\nlet partial (books : Book list) =\n    ul [] [\n        yield!\n            books\n            |\u003e List.map (fun b -\u003e li [] [ str book.Title ])\n    ]\n```\n\nOverall the `Giraffe.ViewEngine` is extremely flexible and more feature rich than any other view engine given that it is just normal compiled F# code.\n\n### Best Practices\n\nDue to the huge amount of available HTML tags and their fairly generic (and short) names (e.g. `\u003cform\u003e`, `\u003coption\u003e`, `\u003cselect\u003e`, etc.) there is a significant danger of accidentally overriding a function of the same name in an application's codebase. For that reason the `Giraffe.ViewEngine` becomes only available after opening the `Giraffe.ViewEngine` module.\n\nAs a measure of good practice it is recommended to create all views in a separate module:\n\n```fsharp\nmodule MyWebApplication\n\nmodule Views =\n    open Giraffe.ViewEngine\n\n    let index =\n        html [] [\n            head [] [\n                title [] [ str \"Giraffe Sample\" ]\n            ]\n            body [] [\n                h1 [] [ str \"I |\u003e F#\" ]\n                p [ _class \"some-css-class\"; _id \"someId\" ] [\n                    str \"Hello World\"\n                ]\n            ]\n        ]\n\n    let other = //...\n```\n\nThis ensures that the opening of the `Giraffe.ViewEngine` is only contained in a small context of an application's codebase and therefore less of a threat to accidental overrides. In the above example views can always be accessed through the `Views` sub module (e.g. `Views.index`).\n\n## Samples\n\nThe following sample code creates and renders a HTML page with the help of the `Giraffe.ViewEngine` and subsequently saves it into a temporary `.html` file before opening it with the default browser:\n\n``` FSharp\nmodule Sample =\n    open Giraffe.ViewEngine\n\n    let bodyTemplate (nameList: string list): XmlNode =\n        body []\n            [ h1 [] [ Text \"Welcome:\" ]\n              ol [] (nameList |\u003e List.map (fun x -\u003e li [] [ Text x ])) ]\n\n    let navTemplate =\n        nav [] [ a [ _href \"./About\" ] [ Text \"About\" ] ]\n\n    let documentTemplate (nav: XmlNode) (body: XmlNode) =\n        html [] [ nav; body ]\n\n    let render welcomeUsers =\n        bodyTemplate welcomeUsers\n        |\u003e (documentTemplate navTemplate)\n        |\u003e RenderView.AsString.htmlDocument\n\n[\u003cEntryPoint\u003e]\nlet main args =\n    let tfn =\n        (System.IO.Path.GetTempFileName()) |\u003e sprintf \"%s.html\"\n\n    args\n    |\u003e Seq.toList\n    |\u003e Sample.render\n    |\u003e fun x -\u003e System.IO.File.WriteAllText(tfn, x)\n    |\u003e ignore\n\n    let p = new System.Diagnostics.Process()\n    p.StartInfo.FileName \u003c- tfn\n    p.StartInfo.UseShellExecute \u003c- true\n    p.Start() |\u003e ignore\n\n    0\n```\n\nThe above code will return the following result:\n\n![alt text][sample-screenshot]\n\n[sample-screenshot]: ./samples/Giraffe.ViewEngine.Sample/sample-screenshot.jpg \"Example for Github\"\n\nThis sample application can also be viewed inside the [Giraffe.ViewEngine.Sample](./samples/Giraffe.ViewEngine.Sample) application.\n\n## Attribution to original authors\n\nThis code has been originally ported from [Suave](https://github.com/SuaveIO/suave) and subsequently improved and extended over the years.\n\nThe original code has been authored by\n\n- [Henrik Feldt](https://github.com/haf)\n- [Ademar Gonzalez](https://github.com/ademar)\n\nYou can find the original implementation here:\n\n[https://github.com/SuaveIO/suave/blob/master/src/Suave.Experimental/Html.fs](https://github.com/SuaveIO/suave/blob/master/src/Suave.Experimental/Html.fs)\n\nHuge thanks to [Suave](https://github.com/SuaveIO/suave) for letting us borrow their code and thanks to [Florian Verdonck](https://github.com/nojaf) for originally porting it to Giraffe.\n\n## Nightly builds and NuGet feed\n\nAll official release packages are published to the official and public NuGet feed.\n\nNightly builds (builds from the `develop` branch) produce unofficial pre-release packages which can be pulled from the [project's NuGet feed on GitHub](https://github.com/orgs/giraffe-fsharp/packages).\n\nThese packages are being tagged with the Workflow's run number as the package version.\n\nAll other builds, such as builds triggered by pull requests produce a NuGet package which can be downloaded as an artifact from the individual GitHub action.\n\n## License\n\n[Apache 2.0](https://raw.githubusercontent.com/giraffe-fsharp/Giraffe.ViewEngine/master/LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiraffe-fsharp%2Fgiraffe.viewengine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgiraffe-fsharp%2Fgiraffe.viewengine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiraffe-fsharp%2Fgiraffe.viewengine/lists"}