How this website is generated

Posted on 2018-08-02 | Last updated on 2021-12-14

Estimated reading time of 3 min.

In the hope that this page is useful to someone, I would like to explain how I generate this website.

Static website generator

I am always looking for an excuse to play with Haskell, and so this website is generated using Hakyll. Hakyll is a static website generator; website configuration is written in Haskell and content is written in Markdown. Hakyll then collects the content, applies templates to it (navigation bar, page footers, etc), and builds a static website in a separate folder. Deploying the website then simply consists in moving the files to the right place.

I use stack to manage the project. stack even has a Hakyll template (hakyll-template).

My site.hs configuration file is fairly standard, but I want to mention two modifications I have made that might help you as well.

Math display and syntax highlighting

This idea is borrowed from JD Reaver

By default, LaTeX-style math is not rendered properly when posts are consumed by Hakyll. We need to provide pandoc - the library that performs the Markdown-to-HTML translation - with a list of extensions to use.

The same problem arises when trying to highlight code. Instead of using the default pandocCompiler, I use a slightly modified compiler that passes extra options to pandoc.

The type signature of our new compiler pandocCompiler_ is:

pandocCompiler_ :: Compiler (Item String)

Exactly the same signature as pandocCompiler, what did you expect? The first step is to collect the relevant Pandoc extensions, which I have separated for clarity:

pandocCompiler_ =
    let
    mathExtensions = 
        [ Ext_tex_math_dollars
        , Ext_tex_math_double_backslash
        , Ext_latex_macros
        ]
    codeExtensions = 
        [ Ext_fenced_code_blocks
        , Ext_backtick_code_blocks
        , Ext_fenced_code_attributes
        ]

The math extensions allows me to write LaTeX code inclusing macros like \begin{align} ... \end{align}. The code extensions allow me to easily switch languages for code blocks.

I then collect all the extensions:

newExtensions = foldr enableExtension defaultExtensions (mathExtensions <> codeExtensions)

Note the use of the mappend function (<>), which could be replaces with list concatenation ++. We modify the Pandoc write options to include these extensions:

defaultExtensions = writerExtensions defaultHakyllWriterOptions
writerOptions =
    defaultHakyllWriterOptions
    { writerExtensions = newExtensions
    , writerHTMLMathMethod = MathJax ""
    , writerHighlightStyle = Just syntaxHighlightingStyle
    }

(Ignore the line writerHighlightStyle = Just syntaxHighlightingStyle for now, this is related to syntax highlighting.) Finally, we create a new compiler that takes those write options into account:

pandocCompilerWith defaultHakyllReaderOptions writerOptions

The full code for this math-and-code compiler is below:

pandocCompiler_ :: Compiler (Item String)
pandocCompiler_ =
    let
    mathExtensions =
        [ Ext_tex_math_dollars
        , Ext_tex_math_double_backslash
        , Ext_latex_macros
        ]
    codeExtensions = 
        [ Ext_fenced_code_blocks
        , Ext_backtick_code_blocks
        , Ext_fenced_code_attributes
        ]
    newExtensions = foldr enableExtension defaultExtensions (mathExtensions <> codeExtensions)
    defaultExtensions = writerExtensions defaultHakyllWriterOptions
    writerOptions =
        defaultHakyllWriterOptions
        { writerExtensions = newExtensions
        , writerHTMLMathMethod = MathJax ""
        , writerHighlightStyle = Just syntaxHighlightingStyle
        }
    in pandocCompilerWith defaultHakyllReaderOptions writerOptions

All pandocCompiler function calls are replaced with pandocCompiler_

Syntax highlighting style as CSS

Pandoc comes with eight syntax highlighting styles:

  • pygments
  • espresso
  • zenburn
  • tango
  • kate
  • monochrome
  • breezeDark
  • haddock

For some reason, there was no difference between the different highlighting styles when generating my website. Therefore, I wanted to generate a CSS file for the style I want to use.

First, I pinned the style I want to use:

syntaxHighlightingStyle :: Style
syntaxHighlightingStyle = haddock

You will recognize the use of this in the above pandocCompiler_. Next, I want to generate CSS for this style with Pandoc and place it with all other CSS files for this website:

generateSyntaxHighlightingCSS :: IO ()
generateSyntaxHighlightingCSS = do
    let css = styleToCss syntaxHighlightingStyle
    writeFile "css/syntax.css" css
    return ()

The syntax.css file must be included in the HTML template:

<link rel="stylesheet" type="text/css" href="/css/syntax.css" />

Finally, before Hakyll does anything, I generate this styling file:

main :: IO ()
main = do

    generateSyntaxHighlightingCSS
    
    hakyll $ do ...

This way, changing the highlighting style is a simple matter of modifying syntaxHighlightingStyle. In the future, I will want to define my own Style (monokai).

Update 2018-08-05 : Generating the HTML templates using Blaze

Hakyll uses HTML templating to enforce consistent styling for all pages of this website. Handwriting HTML is error-prone; therefore, this website uses an HTML template generated using blaze-html.

blaze-html is a domain-specific language - a library of Haskell functions that mimic HTML. For example, the following blaze-html code:

import Text.Blaze.Html5 as H

defaultMain :: H.Html
defaultMain = H.main $ do
    H.h1 "$title$"
    "$body$"

will render to the following HTML:

<main>
    <h1>$title$</h1>
    $body$
</main>

Note that in this case, $title$ and $body$ are placeholders that will be replaced with page content, like a blog post title and text. I generate the HTML template before Hakyll goes to work:

main :: IO ()
main = do
    -- Generate the CSS required to to syntax highlighting
    let css = styleToCss syntaxHighlightingStyle
    writeFile "css/syntax.css" css

    -- Generate the default HTML template
    let template = renderHtml mkDefaultTemplate
    B.writeFile "templates/default.html" template

    hakyll $ do ...

Update 2018-09-12 : using a Pandoc filter to abstract away Bulma quirks

I recently upgraded the website to use the Bulma CSS Framework. There are interesting quirks, like the requirement to have all heading be of a certain class:

<!-- Level-1 title -->
<h1 class="title is-1">Title</h1>

<!-- Level-2 title -->
<h2 class="title is-2">Title</h2>

<!-- Level-1 subtitle -->
<h1 class="subtitle is-1">Title</h1>

To modifying headings generated by Pandoc a posteriori, I wrote a Pandoc filter. The process is documented in this post.