Strongly typed HTML templates with FSharp without a framework

Or do the mundane tasks quickly, and move on to more interesting stuff!

Have you ever had to generate HTML without a web framework?

In most cases HTML generation is tied to web frameworks. Usually you have tooling, examples, tutorials, and all the bells&whistles. If you are using a web framework…

But what if you want to generate HTML in a non-web project? For example send email notifications from a stand-alone service. Or you want to declare some templates once in a non-web project and reuse those in multiple projects?

One obvious way on .NET would be to use Razor, the default view engine for ASP.Net. But if you ever tried to use it in a non-ASP.Net project, you find it kind of painful: you have to reference the whole web stack, have to hack around to get syntax highlight and intellisense working, add additional build steps to precompile and type-check your templates. Along the way you will find some dead or undead projects that don’t work without visual studio, or don’t work in DotNet Core. Not a nice experience…

There other view / template engines for .Net, for example dotliquid, but honestly, didn’t want to learn another template engine, and depend on another package.

Or fall back to manual string replacement. Yeah, that’s a good idea too…

But, is there a solution?

Yes! Enter Paket single file references and the Giraffe ViewEngine!

The what?

  • Paket is dependency manager for .NET project, that among other nice features has the concept of a single file reference: you can declare a dependency just as a file, for example from a GitHub project.
  • Giraffe is an F# web framework, it’s default view engine is just a single file with no additional dependencies. And since views are just regular F# code, it just works: no additional tooling or build steps needed!

All this means, you can just reference Giraffe’s ViewEngine in your project, and use it! No additional dependencies, no framework restrictions, no hacking around or extra build steps! Just use it, create the views, and move on to more interesting things in your life! :)

How to do it?

Create a new F# console project

dotnet new console -lang F# -o src/GiraffeHtmlService

Add paket

Download the latest paket bootstrapper and save it to .paket folder. Follow the instructions here about setting up Paket.

Add GiraffeViewEngine

Add GiraffeViewEngine to your paket.dependecies file (the last line):

Create src/GiraffeHtmlService/paket.references and add a reference to GiraffeViewEngine.fs:

At this point, just check that everything is ok:

Run Paket: Install in VS code, after the install is completed, run the project:

dotnet run — project .\src\GiraffeHtmlService\

And you should see Hello World from F#! in the console!

Creating your first HTML template

Add a new file called HtmlTemplates.fs in src/GiraffeHtmlService. Now it only takes a single string parameter, but you can use anything: it’s just F# code. Use records, helper functions, etc:

Add the file to your fsproj above your Program.fs:

And call it from program.fs:

Run your project again, and you should see glorious HTML output!

> dotnet run --project .\src\GiraffeHtmlService\
The rendered html document:
<!DOCTYPE html>
<html><body><div>Hello World!</div></body></html>

So, a recap

Well, we are done, it’s that simple! You can create HTML in a sane way without a whole web framework!

  • Strongly typed
  • Syntax highlight and intellisense works
  • No additional build step, no project file hacking
  • No dependency on a whole web stack, just add a single file!

And it just works! Spend less time fighting with tooling, and more time on solving actual problems!

The code is up on github.

Gotchas?

Well, obviously, namespaces would collide, if you reference this project in a project that actually uses Giraffe. Haven’t tried, but there are ways to disambiguate types and namespaces in .NET…

And thank you!

The original authors of the code in SuaveIO:

Florian Verdonck for porting it to Giraffe

All contributors of Giraffe, Paket, Ionide, and all other tools!

The whole FSharp community!