// thinkbeforecoding

Full F# Blog

2018-12-07T09:04:00 / jeremie chassaing

this post is part of the F# Advent Calendar 2018

Christmas arrived early this year: my pull request to update FSharp.Formatting to netstandard2.0 was accepted on Oct 22 .

This, combined with a discussion at Open FSharp with Alfonso - famous for creating Fable - led me to use F#, and only F# to publish my blog.

Why not just using wordpress ?

This blog was previously hosted by Gandi on dotclear, and it was ok for years.

Pros:

  • Free
  • Managed
  • No ads

Cons:

  • Hard to style
  • No extensibility
  • Low ownership
  • Painful editing

The editing was really painful in the browser, especialy since Live Writer was not supported anymore. I managed to use FSharp.Formatting for some posts. But when pasting the code in the editor, most of the formatting was rearranged due to platform limitations. I took infinite time to gets the tips working.

But it was free.

I could have found another blogging platform, but most of them are free because ads. Or I could pay. But those plateforms are not necessary better...

And there was anyway an extra challenge: I didn't want to break existing links.

I have some traffic on my blog. External posts, Stack Overflow answers, tweets pointing to my posts. It would be a waste to lose all this.

So it took some time and I finally decided to host it myself.

My stack

The constraint of not breaking existing structure forced me to actually develop my own solution. And since I'm quite fluent in F#... everything would eventually be in F#.

  • F# scripts for building
    • FSharp.Literate to convert md and fsx files to HTML.
    • Fable.React server side rendering for a static full F# template engine
    • FSharp.Data for RSS generation
  • F# script for testing
    • Giraffe to host a local web server to view the result
  • F# script for publishing

The other constraint was price. And since previous solution was free, I took it has a chanlenge to try to make it as cheap as possible. There are lots of free options, but never with custom domain (needed to not break links), and never with https (mandatory since google is showing HTTP sites as unsecure).

I choosed Azure Functions, but with no code. I get:

  • Custom domain for free
  • Free SSL certificate thanks to letsencrypt
  • KeyVault for secret management
  • Staging for deployment
  • Full ownership
  • Almost free (currently around 0.01€/month)

I'll detail the F# part in this post. The azure hosting will be for part 2, and letsencrypt for part 3.

Fake 5

Fake 5 is the tool to write build scripts or simply scripts in F# (thx forki).

To install it, simply type in a command line

1: 
dotnet tool install fake-cli -g

and you're done.

The strength of Fake is that it can reference and load nuget packages using paket (thx again forki) directly in the fsx script:

1: 
2: 
3: 
#r "paket: 
source https://api.nuget.org/v3/index.json
nuget FSharp.Data //"

Fake will then dowload and reference specified packages.

To help with completion at design time you can reference an autogenerated fsx like this:

1: 
#load "../.fake/blog.fsx/intellisense.fsx" 

here I use .. because this blog post is in a subfolder in my project

Packages can then be used:

1: 
open FSharp.Data

FSharp.Literate

FSharp.Formatting is an awsome project to convert F# and MarkDown to HTML.

Conversion to netstandard has been stuck for some time due to its dependency on Razor for HTML templating.

Razor has changed a lot in AspNetCore, and porting existing code was a real nightmare.

To speed up things, I proposed to only port FSharp.Literate and the rest of the project but to get rid of formatting and this dependency on Razor. There is now a beta nuget package deployed on appveyor at https://ci.appveyor.com/nuget/fsharp-formatting : so for my build script I use the following references:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
#r "paket:
source https://api.nuget.org/v3/index.json
source https://ci.appveyor.com/nuget/fsharp-formatting

nuget Fake.IO.FileSystem
nuget Fake.Core.Trace
nuget FSharp.Data
nuget Fable.React
nuget FSharp.Literate //" 

#load "../.fake/blog.fsx/intellisense.fsx" 
1: 
open FSharp.Literate

Markdown

The simplest usage of FSharp.Literate is for posts with no code. In this case, I write it as MarkDown file and convert them using the TransformHtml function:

1: 
2: 
3: 
let md = """# Markdown is cool
especially with *FSharp.Formatting* ! """
            |> FSharp.Markdown.Markdown.TransformHtml

which returns:

"<h1>Markdown is cool</h1>
<p>especially with <em>FSharp.Formatting</em> !</p>
"

Fsx

We can also take a snipet of F# and convert it to HTML:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let snipet  =
    """
    (** # *F# literate* in action *)
    printfn "Hello"
    """
let parse source =
    let doc = 
      let fsharpCoreDir = "-I:" + __SOURCE_DIRECTORY__ + @"\..\lib"
      let systemRuntime = "-r:System.Runtime"
      Literate.ParseScriptString(
                  source, 
                  compilerOptions = systemRuntime + " " + fsharpCoreDir,
                  fsiEvaluator = FSharp.Literate.FsiEvaluator([|fsharpCoreDir|]))
    FSharp.Literate.Literate.FormatLiterateNodes(doc, OutputKind.Html, "", true, true)
let format (doc: LiterateDocument) =
    Formatting.format doc.MarkdownDocument true OutputKind.Html
let fs =
    snipet 
    |> parse
    |> format

The fsharpCoreDir and the -I options are necessary to help FSharp.Literate resolve the path to FSharp.Core. System.Runtime must also be referenced to get tooltips working fine with netstandard assemblies. FSharp interactive is not totally ready for production due to this problem, but with some helps, it works for our need.

Running this code we get:

"<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
<span class="l">2: </span>
<span class="l">3: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp">    <span class="c">(** # *F# literate* in action *)</span>
    <span onmouseout="hideTip(event, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">printfn</span> <span class="s">&quot;Hello&quot;</span>
    
</code></pre></td>
</tr>
</table>
"

As you can see, the code contains a reference to a javascript functions. You can find an implementation on github. It displays type information tool tips generated by the compiler. All the type information is generated during parsing phase:

1: 
2: 
3: 
let tips =
    let doc = parse snipet
    doc.FormattedTips
"<div class="tip" id="1">val printfn : format:Printf.TextWriterFormat&lt;&#39;T&gt; -&gt; &#39;T</div>
"

this way readers get full type inference information in the browser !

But it's even better than that. You can also get the value of some bindings in your ouput:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let values  = 
    """
(** # code execution *)
let square x = x * x
let v = square 3
(** the value is: *)
(*** include-value: v ***)"""
    |> parse
    |> format

and the result is:

"<h1><a name="code-execution" class="anchor" href="#code-execution">code execution</a></h1>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
<span class="l">2: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span class="k">let</span> <span onmouseout="hideTip(event, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, '2', 2)" onmouseover="showTip(event, '2', 2)" class="id">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, '2', 3)" onmouseover="showTip(event, '2', 3)" class="id">x</span> <span class="o">*</span> <span onmouseout="hideTip(event, '2', 4)" onmouseover="showTip(event, '2', 4)" class="id">x</span>
<span class="k">let</span> <span onmouseout="hideTip(event, '3', 5)" onmouseover="showTip(event, '3', 5)" class="id">v</span> <span class="o">=</span> <span onmouseout="hideTip(event, '1', 6)" onmouseover="showTip(event, '1', 6)" class="fn">square</span> <span class="n">3</span>
</code></pre></td>
</tr>
</table>
<p>the value is:</p>
<table class="pre"><tr><td><pre><code>9</code></pre></td></tr></table>
"

You can see that the value of v - 9 - has been computed in the output HTML !

As you can gess, I just used this feature to print the HTML output! Inception !

It also works for printf:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let output  = 
    """
(** # printing *)
let square x = x * x
(*** define-output: result ***)
printfn "result: %d" (square 3)
(** the value is: *)
(*** include-output: result ***)"""
    |> parse
    |> format

Notice the presence of the printf output on the last line:

"<h1><a name="printing" class="anchor" href="#printing">printing</a></h1>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span class="k">let</span> <span onmouseout="hideTip(event, '1', 1)" onmouseover="showTip(event, '1', 1)" class="fn">square</span> <span onmouseout="hideTip(event, '2', 2)" onmouseover="showTip(event, '2', 2)" class="id">x</span> <span class="o">=</span> <span onmouseout="hideTip(event, '2', 3)" onmouseover="showTip(event, '2', 3)" class="id">x</span> <span class="o">*</span> <span onmouseout="hideTip(event, '2', 4)" onmouseover="showTip(event, '2', 4)" class="id">x</span>
</code></pre></td>
</tr>
</table>
<table class="pre"><tr><td class="lines"><pre class="fssnip"><span class="l">1: </span>
</pre></td>
<td class="snippet"><pre class="fssnip highlighted"><code lang="fsharp"><span onmouseout="hideTip(event, '3', 5)" onmouseover="showTip(event, '3', 5)" class="fn">printfn</span> <span class="s">&quot;result: </span><span class="pf">%d</span><span class="s">&quot;</span> <span class="pn">(</span><span onmouseout="hideTip(event, '1', 6)" onmouseover="showTip(event, '1', 6)" class="fn">square</span> <span class="n">3</span><span class="pn">)</span>
</code></pre></td>
</tr>
</table>
<p>the value is:</p>
<table class="pre"><tr><td><pre><code>result: 9</code></pre></td></tr></table>
"

Templating

Now that we can convert the content to HTML, we need to add the surrounding layout.

I use Fable.React for this, but just the server side rendering. So there is no need for the JS tools, only the .net nuget.

After adding the nuget Fable.React in the paket includes, we can open it and start a HTML template:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
open Fable.Helpers.React
open Fable.Helpers.React.Props
open FSharp.Markdown

type Post = {
    title: string
    content: string
}

let template post = 
    html [Lang "en"] [
        head [] [
            title [] [ str ("My blog / " + post.title) ]
        ]
        body [] [
            RawText post.content
        ]
    ]

to convert it too string, we simply add the doctype to make it HTML5 compatible and use renderToString

1: 
2: 
3: 
4: 
5: 
6: 
let render html =
  fragment [] [ 
    RawText "<!doctype html>"
    RawText "\n" 
    html ]
  |> Fable.Helpers.ReactServer.renderToString 

let's use it :

1: 
2: 
3: 
4: 
5: 
let myblog =
    { title = "super post"
      content = Markdown.TransformHtml "# **interesting** things" }
    |> template
    |> render 

now we get the final page:

"<!doctype html>
<html data-reactroot="" lang="en"><head><title>My blog / super post</title></head><body><h1><strong>interesting</strong> things</h1>
</body></html>"

RSS

Rss has lost attraction lately, but I still have requests every day on the atom feed.

Using Fsharp data, generating the RSS feed is straight forward:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
#I @"..\packages\full\FSharp.Data\lib\netstandard2.0\"
#r "System.Xml.Linq"
#r "FSharp.Data"
open System
open FSharp.Data
open System.Security.Cryptography

[<Literal>]
let feedXml = """<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xml:lang="en">
  
  <title type="html">Think Before Coding</title>
  <link href="http://thinkbeforecoding.com:82/feed/atom" rel="self" type="application/atom+xml"/>
  <link href="http://thinkbeforecoding.com/" rel="alternate" type="text/html"
  title=""/>
  <updated>2017-12-09T01:20:21+01:00</updated>
  <author>
    <name>Jérémie Chassaing</name>
  </author>
  <id>urn:md5:18477</id>
  <generator uri="http://www.dotclear.net/">Dotclear</generator>
  <entry>
    <title>fck: Fake Construction Kit</title>
    <link href="http://thinkbeforecoding.com/post/2016/12/04/fck%3A-Fake-Construction-Kit" rel="alternate" type="text/html"
    title="fck: Fake Construction Kit" />
    <id>urn:md5:d78962772329a428a89ca9d77ae1a56b</id>
    <updated>2016-12-04T10:34:00+01:00</updated>
    <author><name>Jérémie Chassaing</name></author>
        <dc:subject>f</dc:subject><dc:subject>FsAdvent</dc:subject>    
    <content type="html">    &lt;p&gt;Yeah it's christmas time again, and santa's elves are quite busy.&lt;/p&gt;
ll name: Microsoft.FSharp.Core.Operators.not&lt;/div&gt;</content>
      </entry>
  <entry>
    <title>Ukulele Fun for XMas !</title>
    <link href="http://thinkbeforecoding.com/post/2015/12/17/Ukulele-Fun-for-XMas-%21" rel="alternate" type="text/html"
    title="Ukulele Fun for XMas !" />
    <id>urn:md5:5919e73c387df2af043bd531ea6edf47</id>
    <updated>2015-12-17T10:44:00+01:00</updated>
    <author><name>Jérémie Chassaing</name></author>
        <dc:subject>F#</dc:subject>
    <content type="html">    &lt;div style=&quot;margin-top:30px&quot; class=&quot;container row&quot;&gt;
lt;/div&gt;</content>
      </entry>
</feed>"""

type Rss = XmlProvider<feedXml>
let links: Rss.Link[] = [|
    Rss.Link("https://thinkbeforecoding.com/feed/atom","self", "application/atom+xml", null)
    Rss.Link("https://thinkbeforecoding.com/","alternate", "text/html", "thinkbeforecoding")
    |]

let entry title link date content = 
    let md5Csp = MD5CryptoServiceProvider.Create()
    let md5 =
        md5Csp.ComputeHash(Text.Encoding.UTF8.GetBytes(content: string))
        |> Array.map (sprintf "%2x")
        |> String.concat ""
        |> (+) "urn:md5:"

    Rss.Entry(
        title,
        Rss.Link2(link, "alternate", "text/html", title),
        md5,
        DateTimeOffset.op_Implicit date,
        Rss.Author2("Jérémie Chassaing"),
        [||],
        Rss.Content("html", content)
        )
let feed entries =
    Rss.Feed("en", 
        Rss.Title("html","thinkbeforecoding"), 
        links,DateTimeOffset.UtcNow, 
        Rss.Author("Jérémie Chassaing"),
        "urn:md5:18477",
        Rss.Generator("https://fsharp.org","F# script"),
        List.toArray entries
         )

just pass all posts to the feed function, and you get a full RSS feed.

Migration

To migrate from my previous blog, I exported all data to csv, and used the CSV type provider to parse it.

I extracted the HTML and put it in files, and generated a fsx file containing a list of posts with metadata:

  • title
  • date
  • url
  • category

Once done, I just have to map the post list using conversion and templates, and I have my new blog.

Wrapping it up

Using F# tools, I get easily a full control on my blog. And all this in my favorite language!

See you in next part about hosting in Azure.

Happy Christmas!

Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
namespace FSharp.Literate
val md : string
namespace FSharp.Markdown
type Markdown =
  static member Parse : text:string -> MarkdownDocument
  static member Parse : text:string * newline:string -> MarkdownDocument
  static member TransformHtml : text:string -> string
  static member TransformHtml : text:string * newline:string -> string
  static member TransformHtml : text:string * writer:TextWriter -> unit
  static member TransformHtml : text:string * writer:TextWriter * newline:string -> unit
  static member TransformLatex : text:string -> string
  static member TransformLatex : text:string * newline:string -> string
  static member TransformLatex : text:string * writer:TextWriter -> unit
  static member TransformLatex : text:string * writer:TextWriter * newline:string -> unit
  ...
static member FSharp.Markdown.Markdown.TransformHtml : text:string -> string
static member FSharp.Markdown.Markdown.TransformHtml : text:string * newline:string -> string
static member FSharp.Markdown.Markdown.TransformHtml : text:string * writer:System.IO.TextWriter -> unit
static member FSharp.Markdown.Markdown.TransformHtml : text:string * writer:System.IO.TextWriter * newline:string -> unit
val snipet : string
val parse : source:string -> LiterateDocument
val source : string
val doc : LiterateDocument
val fsharpCoreDir : string
val systemRuntime : string
type Literate =
  private new : unit -> Literate
  static member FormatLiterateNodes : doc:LiterateDocument * ?format:OutputKind * ?prefix:string * ?lineNumbers:bool * ?generateAnchors:bool -> LiterateDocument
  static member ParseMarkdownFile : path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseMarkdownString : content:string * ?path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseScriptFile : path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ParseScriptString : content:string * ?path:string * ?formatAgent:CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
  static member ProcessDirectory : inputDirectory:string * ?outputDirectory:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?fsiEvaluator:IFsiEvaluator * ?replacements:(string * string) list * ?includeSource:bool * ?generateAnchors:bool * ?processRecursive:bool * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> (string * GeneratorOutput) list
  static member ProcessDocument : doc:LiterateDocument * output:string * ?format:OutputKind * ?prefix:string * ?lineNumbers:bool * ?includeSource:bool * ?generateAnchors:bool * ?replacements:(string * string) list -> GeneratorOutput
  static member ProcessMarkdown : input:string * ?output:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?replacements:(string * string) list * ?includeSource:bool * ?generateAnchors:bool * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> GeneratorOutput
  static member ProcessScriptFile : input:string * ?output:string * ?format:OutputKind * ?formatAgent:CodeFormatAgent * ?prefix:string * ?compilerOptions:string * ?lineNumbers:bool * ?references:bool * ?fsiEvaluator:IFsiEvaluator * ?replacements:(string * string) list * ?includeSource:bool * ?generateAnchors:bool * ?customizeDocument:(ProcessingContext -> LiterateDocument -> LiterateDocument) -> GeneratorOutput
  ...
static member Literate.ParseScriptString : content:string * ?path:string * ?formatAgent:FSharp.CodeFormat.CodeFormatAgent * ?compilerOptions:string * ?definedSymbols:#seq<string> * ?references:bool * ?fsiEvaluator:IFsiEvaluator -> LiterateDocument
Multiple items
type FsiEvaluator =
  interface IFsiEvaluator
  new : ?options:string [] * ?fsiObj:obj -> FsiEvaluator
  member RegisterTransformation : f:(obj * Type -> MarkdownParagraph list option) -> unit
  member EvaluationFailed : IEvent<FsiEvaluationFailedInfo>

--------------------
new : ?options:string [] * ?fsiObj:obj -> FsiEvaluator
static member Literate.FormatLiterateNodes : doc:LiterateDocument * ?format:OutputKind * ?prefix:string * ?lineNumbers:bool * ?generateAnchors:bool -> LiterateDocument
type OutputKind =
  | Html
  | Latex
union case OutputKind.Html: OutputKind
val format : doc:LiterateDocument -> string
Multiple items
type LiterateDocument =
  new : paragraphs:MarkdownParagraphs * formattedTips:string * links:IDictionary<string,(string * string option)> * source:LiterateSource * sourceFile:string * errors:seq<SourceError> -> LiterateDocument
  override Equals : other:obj -> bool
  override GetHashCode : unit -> int
  member With : ?paragraphs:MarkdownParagraphs * ?formattedTips:string * ?definedLinks:IDictionary<string,(string * string option)> * ?source:LiterateSource * ?sourceFile:string * ?errors:seq<SourceError> -> LiterateDocument
  member DefinedLinks : IDictionary<string,(string * string option)>
  member Errors : seq<SourceError>
  member FormattedTips : string
  member MarkdownDocument : MarkdownDocument
  member Paragraphs : MarkdownParagraphs
  member Source : LiterateSource
  ...

--------------------
new : paragraphs:FSharp.Markdown.MarkdownParagraphs * formattedTips:string * links:System.Collections.Generic.IDictionary<string,(string * string option)> * source:LiterateSource * sourceFile:string * errors:seq<FSharp.CodeFormat.SourceError> -> LiterateDocument
module Formatting

from FSharp.Literate
val format : doc:FSharp.Markdown.MarkdownDocument -> generateAnchors:bool -> outputKind:OutputKind -> string
property LiterateDocument.MarkdownDocument: FSharp.Markdown.MarkdownDocument
val fs : string
val tips : string
property LiterateDocument.FormattedTips: string
val values : string
val output : string
namespace Fable
namespace Fable.Helpers
module React

from Fable.Helpers
module Props

from Fable.Helpers.React
type Post =
  {title: string;
   content: string;}
Post.title: string
Multiple items
val string : value:'T -> string

--------------------
type string = System.String
Post.content: string
val template : post:Post -> Fable.Import.React.ReactElement
val post : Post
val html : b:seq<IHTMLProp> -> c:seq<Fable.Import.React.ReactElement> -> Fable.Import.React.ReactElement
union case HTMLAttr.Lang: string -> HTMLAttr
val head : b:seq<IHTMLProp> -> c:seq<Fable.Import.React.ReactElement> -> Fable.Import.React.ReactElement
val title : b:seq<IHTMLProp> -> c:seq<Fable.Import.React.ReactElement> -> Fable.Import.React.ReactElement
val str : s:string -> Fable.Import.React.ReactElement
val body : b:seq<IHTMLProp> -> c:seq<Fable.Import.React.ReactElement> -> Fable.Import.React.ReactElement
union case HTMLNode.RawText: string -> HTMLNode
val render : html:Fable.Import.React.ReactElement -> string
val html : Fable.Import.React.ReactElement
val fragment : props:seq<IFragmentProp> -> children:seq<Fable.Import.React.ReactElement> -> Fable.Import.React.ReactElement
module ReactServer

from Fable.Helpers
val renderToString : htmlNode:Fable.Import.React.ReactElement -> string
val myblog : string
static member Markdown.TransformHtml : text:string -> string
static member Markdown.TransformHtml : text:string * newline:string -> string
static member Markdown.TransformHtml : text:string * writer:System.IO.TextWriter -> unit
static member Markdown.TransformHtml : text:string * writer:System.IO.TextWriter * newline:string -> unit
namespace System
namespace System.Security
namespace System.Security.Cryptography
Multiple items

--------------------
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val feedXml : string
type Rss = obj
val links : obj []
val entry : title:'a -> link:'b -> date:'c -> content:'d -> 'e
val title : 'a
val link : 'b
val date : 'c
val content : 'd
val md5Csp : obj
val md5 : string
Multiple items
union case HTMLNode.Text: string -> HTMLNode

--------------------
namespace System.Text
type Encoding =
  member BodyName : string
  member Clone : unit -> obj
  member CodePage : int
  member DecoderFallback : DecoderFallback with get, set
  member EncoderFallback : EncoderFallback with get, set
  member EncodingName : string
  member Equals : value:obj -> bool
  member GetByteCount : chars:char[] -> int + 5 overloads
  member GetBytes : chars:char[] -> byte[] + 7 overloads
  member GetCharCount : bytes:byte[] -> int + 3 overloads
  ...
property Text.Encoding.UTF8: Text.Encoding
Text.Encoding.GetBytes(s: string) : byte []
Text.Encoding.GetBytes(chars: char []) : byte []
Text.Encoding.GetBytes(chars: ReadOnlySpan<char>, bytes: Span<byte>) : int
Text.Encoding.GetBytes(s: string, index: int, count: int) : byte []
Text.Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
Text.Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
Multiple items
val string : value:'T -> string

--------------------
type string = String
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []
val sprintf : format:Printf.StringFormat<'T> -> 'T
Multiple items
type String =
  new : value:char[] -> string + 8 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool + 3 overloads
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 3 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int + 1 overload
  ...

--------------------
String(value: char []) : String
String(value: nativeptr<char>) : String
String(value: nativeptr<sbyte>) : String
String(value: ReadOnlySpan<char>) : String
String(c: char, count: int) : String
String(value: char [], startIndex: int, length: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
val concat : sep:string -> strings:seq<string> -> string
Multiple items
type DateTimeOffset =
  struct
    new : dateTime:DateTime -> DateTimeOffset + 5 overloads
    member Add : timeSpan:TimeSpan -> DateTimeOffset
    member AddDays : days:float -> DateTimeOffset
    member AddHours : hours:float -> DateTimeOffset
    member AddMilliseconds : milliseconds:float -> DateTimeOffset
    member AddMinutes : minutes:float -> DateTimeOffset
    member AddMonths : months:int -> DateTimeOffset
    member AddSeconds : seconds:float -> DateTimeOffset
    member AddTicks : ticks:int64 -> DateTimeOffset
    member AddYears : years:int -> DateTimeOffset
    ...
  end

--------------------
DateTimeOffset ()
DateTimeOffset(dateTime: DateTime) : DateTimeOffset
DateTimeOffset(ticks: int64, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(dateTime: DateTime, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
DateTimeOffset.op_Implicit(dateTime: DateTime) : DateTimeOffset
val feed : entries:'a -> 'b
val entries : 'a
property DateTimeOffset.UtcNow: DateTimeOffset
Multiple items
union case HTMLAttr.List: string -> HTMLAttr

--------------------
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
    interface IReadOnlyList<'T>
    interface IReadOnlyCollection<'T>
    interface IEnumerable
    interface IEnumerable<'T>
    member GetSlice : startIndex:int option * endIndex:int option -> 'T list
    member Head : 'T
    member IsEmpty : bool
    member Item : index:int -> 'T with get
    member Length : int
    member Tail : 'T list
    ...
val toArray : list:'T list -> 'T []