Moving to Hakyll

Posted on 2017-07-17

My blog previously used Octopress, and I had wanted to stop using it for a while. There is nothing wrong with Octopress; it just didn’t fit well with me. It has a lot of features, so seems overcomplicated for my use-case. It’s also written in Ruby.

Ruby is the main reason for quitting Octopress. I don’t know it very well, and have little desire to know it more than I currently do. With Octopress, I have a limited understanding about how my blog actually works. I’d like to extend it to do various things, but that would require learning the inner workings of Octopress. Also, Ruby sometimes randomly breaks on my computer, and it is a hassle trying to fix it. Ruby dependency conflicts seem to be an eternal source of pain for people on the Internet. It doesn’t fill me with confidence.

Ossi Hanhinen tweeted about moving his blog from Jekyll to Hakyll a few months ago, and I knew instantly that Hakyll was something I should investigate. Crucially, it is written in Haskell, a language that I’m still excited about learning. Also, all the logic is also contained in a smallish site.hs file, so it is easy to understand and extend.

Migrating Posts

Hakyll and Octopress are both static-site generators which transform Markdown into HTML. After initialising my Hakyll repo, a simple cp * command copied all of my Octopress posts into the Hakyll directory, and it mostly worked straight away. There were a few minor changes needed.

One of the first things required was reformatting any code examples. Octopress required code formatted inside code blocks like this:

 {% codeblock lang:haskell %}
 // code in here
 {% endcodeblock %}

Whereas Hakyll requires code blocks like this:

 ``` Haskell
 // code in here
 ```

This was quite a trivial change, but nevertheless an important one.

There were a few changes I made to customise Haykll. Providing an Atom Feed for my Haykll site, for example. Octopress did this out of the box. Hakyll has a tutorial to guide you through adding it in. Cleaning up the URLs was also necessary, to remove the date paths and .html extensions of the static pages.

Merge, and compress, CSS Assets

Hakyll provides a compressCssCompiler in order to reduce the size of your CSS. However, it doesn’t automatically merge the css into one file. Having one CSS file is recommended by site-speed-tests

Luckily, the Haykll Google Group has a small thread about merging assets. I slightly altered the code from that thread, in order to merge and compress the CSS. This is where Haykll starts to shine, as small bits of Haskell can do very powerful things.

-- merge css assets together, and compress
-- https://groups.google.com/forum/#!topic/hakyll/AIkHo1uyZoY
match "css/*" $ compile getResourceBody
create ["master.css"] $ do
    route   idRoute
    compile $ do
      items <- loadAll "css/*"
      makeItem $ compressCss $ concatMap itemBody (items :: [Item String])

Elm

One of the slightly different things about my Octopress blog was that it had an example of using Elm in an Octopress blog post. For those that don’t know, Elm compiles to JavaScript. Therefore, simply including it in an HTML page means it is available to run.

Hakyll does not include a compressJsCompiler though. Luckily, the Google Group, and a blog on the Internet came to my rescue, and provided an easy way to compress the Elm code after it had been transpiled to JavaScript:

match "js/**" $ do
  route   idRoute
  compile compressJsCompiler
import qualified Data.ByteString.Lazy.Char8 as C
import           Text.Jasmine

compressJsCompiler :: Compiler (Item String)
compressJsCompiler = do
  let minifyJS = C.unpack . minify . C.pack . itemBody
  s <- getResourceString
  return $ itemSetBody (minifyJS s) s

Deploying it to Gitlab Pages

With a new blog engine, it was also time to try a new deployment mechanism. Gitlab has Pages and also Pipelines. Combining the two was relatively simple, and means that a commit to the git repo triggers a pipeline run to deploy a new version of my blog.

Gitlab Pages uses a YAML file named .gitlab-ci.yml to configure the pipeline. The YAML below takes the official Haskell docker image, installs and runs Hakyll, and then moves all the static pages to the public directory so that Pages will display them.

image: haskell:latest

before_script:
  - apt-get update && apt-get install xz-utils make

pages:
  cache:
    paths:
      - _cache
      - .stack-work
  script:
  - stack setup
  - stack build
  - stack exec site build
  - mv _site public
  artifacts:
    paths:
    - public
  only:
    - master

Unfortunately, this takes around 30 minutes from git commit to site change! There is an open Gitlab issue full of information to speed up Hakyll in Gitlab Pipelines. I mitigated the impact of the 30 minute wait by only committing before I went to bed. It had deployed by the morning.

Overall, I’m super happy about Hakyll. I have a small list of things that still need to be done to my blog, but I’m confident, and excited, about learning more Haskell and Hakyll.