Create static website yourself. No 3rd party services
Tags: fsharp, webdev, github, opensource
Published: 2022-07-20 08:25:35

... except github.

I didn't mention in the title the language we're going to use, because at this point I'm gonna use the **right tool**. The right tool being .NET, more specifically, F#.

[.NET SDK](https://dotnet.microsoft.com/en-us/download) is required.

So this is a short step-by-step guide.

## 1. Create a repository on github.com

This one is easy. I'm gonna create a repo with some list of repos.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9aqmg01tj70pgy5jx8j.png)
 
## 2. Clone it locally

I'm gonna open my favorite terminal and type
```
git clone https://github.com/WhiteBlackGoose/MyFunRepos
cd MyFunRepos
```

## 3. Initialize the project

```
dotnet new console -n WebsiteGenerator -lang F#
cd WebsiteGenerator
dotnet add package Giraffe.ViewEngine --prerelease
```

This creates you an project in F#! Also adds a package that allows to create html tags right in code. Soon about it.

## 4. Open the project

We're creating something very simple, so you can either open `Program.fs` with your favorite text editor, or just open the `fsproj` file with an IDE.

I'm gonna use nvim for it.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fpsmtjwz7fqn0so98isj.png)
 
## 5. Create your first webpage

Let's create an empty html page which just has a header and some random text.
```fs
﻿open Giraffe.ViewEngine


html [] [
    body [] [
        h1 [] [ Text "Hello, world" ]
        p [] [
            Text "Some text"
        ]
    ]
]
|> RenderView.AsString.htmlNode
|> fun code -> System.IO.File.WriteAllText("./index.html",  code)
```

Quite readable, right? Let's run it!

```
dotnet run
firefox index.html
```
If you don't have firefox, you can try
```
xdg-open index.html  # linux
start index.html     # windows
```
or just open the html page from your file explorer!

Works for me:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0u1p3z87wsb55y7x0srj.png)
 
## 6. Add some data to it

You could've made that page with just html. Why do you need F# for it?

Okay, let's complicate the story. This time we're gonna add a list of data and some condition.

```fs
﻿open Giraffe.ViewEngine

type Repo = {
    name : string
    lang : string
}

let myFunReposList = [
    { name = "MyFunRepos";             lang = "F#" }
    { name = "FStarHelloWorld";        lang = "F*" }
    { name = "LambdaCalculusFSharp";   lang = "F#" }
    { name = "AsmToDelegate";          lang = "C#" }
]

html [] [
    body [] [
        h1 [] [ Text "My Fun Repos" ]
        ul [] [
            for { name = name; lang = lang } in myFunReposList do
                li [] [
                    p [] [ Text $"Repo {name}" ]
                    a [_href $"https://github.com/WhiteBlackGoose/{name}"] [ Text name ]
                    p [] [
                        Text "Made in "
                        if lang = "F#" || lang = "C#" then
                            Text $".NET {lang}"
                        else
                            Text lang
                    ]
                ]
        ]        
    ]
]
|> RenderView.AsString.htmlNode
|> fun code -> System.IO.File.WriteAllText("./index.html",  code)

```

Okay, what are we doing here? We created a record `Repo`, which has two fields (for simplicity). Then I'm creating a list of my repos willing those fields.

There's no string concatenation and no hell with parentheses. We don't even have commas in the end of each line.

Now the most interesting part. See, I'm inserting a regular `for` loop right in the `ul` element! I'm not creating it in a separate variable or function or anything, I *just* write it inside the hmtl tag. As if it's some template engine, but it's not, it's a regular language.

I even inserted an `if` condition there, which also reads very well!

Let's see what we got for this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/riubi6abbmbl93lwz1o1.png)
 
This created a simple html page. That's it. No template engine. This code is written in *one* programming language, and you can debug it. You're in full control!

## 7. Automatic website creation

Ok, we made an html page. But it's not a website yet. What we want is that our website could be automatically published on every push. So let's write our Github Actions workflow.

```yml
name: Website publish

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    - name: Setup .NET 6
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '6.0.x'

    - name: Run generator
      run: |
        mkdir generated && cd generated
        dotnet run --project ../WebsiteGenerator/WebsiteGenerator.fsproj
    
    - name: Push to gh-pages
      uses: JamesIves/github-pages-deploy-action@4.1.4
      with:
        branch: gh-pages
        folder: ./generated
```

The file is quite readable on its own. We install .NET SDK, then we run our generator from a certain folder, and then we just push the folder to the `gh-pages` branch! That is it.

## 8. Activate Pages

1. Go to your repo's settings
2. Go to Pages
3. Choose gh-pages as source branch
4. Press Save

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1qjhttyqj61kmpmyhfb.png)
 

## 9. Enjoy the result!

My url looks like this: [whiteblackgoose.github.io/MyFunRepos/](https://whiteblackgoose.github.io/MyFunRepos/).

Now I can easily add content to the page, or create many pages, as well as add some data, maybe even downloading some dataset and then displaying it.

# Summary

We created a simple static website in F#! Here's main features of this setup:
- You have full control of it. No static website generator were used
- Syntax of F# is really handy here. You can insert loops, conditionals, and some other control flow constructions right in the page!
- Free hosting and free default domain (but you can set your own domain if you want)
- It takes 5-30 minutes to set up

Thanks for your attention! My [github](https://github.com/WhiteBlackGoose), [twitter](https://twitter.com/WhiteBlackGoose).

[Sources](https://github.com/WhiteBlackGoose/MyFunRepos)