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:
= foldr enableExtension defaultExtensions (mathExtensions <> codeExtensions) newExtensions
Note the use of the mappend
function (<>
), which could be replaces with list concatenation ++
. We modify the Pandoc write options to include these extensions:
= writerExtensions defaultHakyllWriterOptions
defaultExtensions =
writerOptions
defaultHakyllWriterOptions= newExtensions
{ writerExtensions = MathJax ""
, writerHTMLMathMethod = Just syntaxHighlightingStyle
, writerHighlightStyle }
(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
,
]= foldr enableExtension defaultExtensions (mathExtensions <> codeExtensions)
newExtensions = writerExtensions defaultHakyllWriterOptions
defaultExtensions =
writerOptions
defaultHakyllWriterOptions= newExtensions
{ writerExtensions = MathJax ""
, writerHTMLMathMethod = Just syntaxHighlightingStyle
, writerHighlightStyle
}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
= haddock syntaxHighlightingStyle
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 ()
= do
generateSyntaxHighlightingCSS 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 ()
= do
main
generateSyntaxHighlightingCSS
$ do ... hakyll
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
= H.main $ do
defaultMain "$title$"
H.h1 "$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 ()
= do
main -- 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
"templates/default.html" template
B.writeFile
$ do ... hakyll
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.