Hakyll Pt. 1 – Setup & Initial Customization
This is part 1 of a multipart series where we will look at getting a website / blog set up with hakyll and customized a fair bit.
- Pt. 1 – Setup & Initial Customization
- Pt. 2 – Generating a Sitemap XML File
- Pt. 3 – Generating RSS and Atom XML Feeds
- Pt. 4 – Copying Static Files For Your Build
- Pt. 5 – Generating Custom Post Filenames From a Title Slug
- Pt. 6 – Pure Builds With Nix
- (wip) Pt. 7 – Customizing Markdown Compiler Options
Overview
Installation & Setup
While this is detailed fully on the hakyll installation tutorial, I will repeat it here.
- install stack and make
sure
$HOME/.local/bin
is included in yourPATH
λ stack install hakyll
– should installhakyll-init
in$HOME/.local/bin
λ hakyll-init ourblog.com
λ cd ourblog.com
λ stack init
λ stack build
λ stack exec site build
λ stack exec site rebuild
– to test the rebuild commandλ stack exec site watch
– starts dev server & watches for changes- navigate to http://localhost:8000 to see it!
Configuration Rules
Hakyll gives you the ability to override its existing configuration
rules to
change anything from the output directory (default _site/
) to deploy commands
to the host and port for previewing your site locally.
Here is what the default configuration looks like in hakyll (source):
-- | Default configuration for a hakyll application
defaultConfiguration :: Configuration
= Configuration
defaultConfiguration = "_site"
{ destinationDirectory = "_cache"
, storeDirectory = "_cache/tmp"
, tmpDirectory = "."
, providerDirectory = ignoreFile'
, ignoreFile = "echo 'No deploy command specified' && exit 1"
, deployCommand = system . deployCommand
, deploySite = True
, inMemoryCache = "127.0.0.1"
, previewHost = 8000
, previewPort
}where
ignoreFile' path| "." `isPrefixOf` fileName = True
| "#" `isPrefixOf` fileName = True
| "~" `isSuffixOf` fileName = True
| ".swp" `isSuffixOf` fileName = True
| otherwise = False
where
= takeFileName path fileName
The hakyll tutorial on rules, routes and
compilers
makes reference to a hakyllWith
function for customizing configuration, so
let’s see how we can use that.
The default hakyll main
function in your site.hs
file looks like this:
main :: IO ()
= hakyll $ do main
What we can do is change hakyll
to hakyllWith
and pass a function that
we’ll name config
that makes use of the defaultConfiguration
but returns a
new, altered record:
main :: IO ()
= hakyllWith config $ do
main -- ...
config :: Configuration
= defaultConfiguration
config = "docs"
{ destinationDirectory = 5000
, previewPort }
Whenever we make a change to site.hs
, we need to make sure we use stack
to
build
it again and restart our server. We’ll also need to make sure we clean
out our old output folder with the clean
command. So, all together now:
λ stack exec site clean
λ stack build
λ stack exec site watch
…and now your output will be in the docs/
folder, and your site will be
previewable at http://localhost:5000.
Now that we’ve flexed our configuration muscles a bit, let’s look at the
posts/
folder to see what we’re working with on the blog side.
Blog Posts
If you open the posts/
folder and select any preset blog post (hint: you can
see them online at
https://github.com/jaspervdj/hakyll/tree/master/data/example/posts; make sure
you click the “Raw” button to view the raw markdown), you’ll see a standard
markdown file containing two sets of content:
* metadata (between the ---
delimiters)
* body content (everything else)
From http://localhost:5000, let’s click on the first post we see:
http://localhost:5000/posts/2015-12-07-tu-quoque.html. If we open up the
corresponding file, 2015-12-07-tu-quoque.html
, in our text editor, we can see
there are two metadata fields: title
and author
. Let’s change them:
---
title: Some Latin Text
author: Some Roman Person
---
Refresh the page and see the changes!
But note that despite changing the title of your blog post, the outputted HTML file is still located at http://localhost:5000/posts/2015-12-07-tu-quoque.html. This is because the markdown filename is what currently determines the outputted filename. We will change this in Part 5 of this series, but until then, if you change the title of your post, it would be a good idea to also change the filename.
Feel free to edit these metadata fields and markdown content with your own blog post material.
Next up, we’ll see about how we can customize the templates to work with all the metadata that we might want to include from our posts (description, author, keywords, image, etc).
Working With Templates
There is a hakyll turorial on templates, context and control flow that you should check out. Here, we’re going to adjust the default templates to suit our needs.
The HTML templates can be found in – you guessed it – the templates/
folder.
The first file we will look at is templates/default.html
(hint: this template
is also viewable online at
https://github.com/jaspervdj/hakyll/blob/master/data/example/templates/default.html).
Templates are nothing more than .html
files but with a caveat (which you’d
know about if you read the tutorial above): there is added context – drawn from
markdown options or injected before compilation in site.hs
– that can be
used anywhere, so long as it is between $
(dollar signs). Here is an example
that uses the title
property that is set in each file:
<title>$title$</title>
Cool! Now what if we wanted to use our author
metadata?
<meta name="author" content="$author$">
Oh no!
Compiling
updated templates/default.html
[ERROR] Missing field $author$ in context for item about.rst
This is because not all of our files being run through this default template have all the same fields. We can use conditionals to solve this:
<meta name="author" content="$author$">$endif$
$if(author)$
<!-- or, if you prefer -->
$if(author)$<meta name="author" content="$author$">
$endif$
Blog posts also should have a description
and keywords
, so let’s add those:
to posts/2015-12-07-tu-quoque.markdown
:
---
title: My Blog Post
description: This is my great blog post
keywords: blog, first blog, best blog evar
author: I did it!
---
We’ll then update our default template to handle those, as well:
<title>$title$</title>
<meta name="author" content="$author$">$endif$
$if(author)$<meta name="keywords" content="$keywords$">$endif$ $if(keywords)$
If you refresh http://localhost:5000/posts/2015-12-07-tu-quoque.html and open up
the web inspector, you’ll now see that the <head>
now contains not only your
post’s title
, but also all the other fields you specified!
There are many other possibilities for this, as well. For instance, if you
wanted to have different og:type
s of pages, you could do:
$if(type)$<meta property="og:type" content="$type$">
$else$<meta property="og:type" content="website">
$endif$
Check out the default template for this website here: https://github.com/rpearce/robertwpearce.com/blob/main/src/templates/default.html.
Rendering Partials
Lastly for today, what if we want to reuse templates and specify where they should be rendered from other templates? Enter hakyll partials.
A common use of partials is for navigation across different templates. We can
add a new file, templates/nav.html
, and place the following in it (add some
CSS classes and styling if you want it to look nice):
<nav class="nav">
<a href="/">Home</a>
<a href="mailto:[email protected]">Email Me</a>
</nav>
Now, this partial can be used anywhere. For example, from
templates/post.html
:
$partial("templates/nav.html")$
Wrapping Up
In this lesson we learned how to get started with
hakyll and learned some of the ways for us to get
started customizing it to our own needs. Next time, we’ll dive into site.hs
to generate our own sitemap.xml
file.
Next up: * Pt. 2 – Generating a Sitemap XML File * Pt. 3 – Generating RSS and Atom XML Feeds * Pt. 4 – Copying Static Files For Your Build * Pt. 5 – Generating Custom Post Filenames From a Title Slug * (wip) Pt. 6 – Customizing Markdown Compiler Options
Until next time,
Robert