Motivation
I had some time off last week and almost immediately caught a nasty cold, so instead of spending my time on the beach, I tried to keep myself busy while staying in bed. So, as a result, this blog is now built with Hakyll instead of Pelican.
Why Switch?
Pelican is a great tool, it’s what Codeberg uses to publish their blog. It makes it easy to get started by requiring a strict directory structure; based on that and a configuration file, it does a lot of things out of the box like creating an RSS feed. However, it also does a lot of things that I don’t need, like generating tags.
I stumbled across Hakyll on kotatsuyaki’s blog and was intrigued. Hakyll is not a Static Site Generator per se; it is a Haskell library that allows you to specify the generation of your site declaratively with an embedded DSL.
And so it was decided; if I wasn’t going to get my feet wet in the ocean I could at least play around with Haskell.
The Setup
The site generator is easy enough to write; the DSL is quite intuitive.
-- site.hs
main :: IO ()
= hakyll $ do
main "css/*" $ do
match
route idRoute
compile compressCssCompiler
"posts/*" $ do
match $ setExtension "html"
route $ pandocCompiler
compile >>= loadAndApplyTemplate "templates/post.html" postCtx
>>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls
--...
If you are interested in the full source code, you can find it here. Compiling this yields an executable site
which builds the static content from the markdown files in the posts
directory.
The main piece that pulls this setup together is the Nix flake. It declares one package:
with pkgs; stdenv.mkDerivation {
packages.ssg = name = "ssg";
src = self;
nativeBuildInputs = [
( haskellPackages.ghcWithPackages (ps: with ps; [
cabal-install
openssh
zlib
hakyll])
)
];
buildInputs = [ ];
# needed to avoid trying to build in a read-only directory
preBuild = "export HOME=$TMPDIR";
installPhase = ''
ghc --make site.hs
mkdir -p $out/bin
install -t $out/bin site
'';
outputs = [ "out" ];
};
packages.default = packages.ssg;
This declares the build steps and dependencies for the static site generator. The command nix shell
will build the site
executable and enter a shell in which this executable is available, giving us access to the subcommands site build
, site clean
, site watch
. Once we have the executable, we no longer need any of the build dependencies, such as the Haskell compiler, when we just want to write content, so they are not available in this shell.
When we do want to make changes to the static site generator, we can instead use nix develop
. This gives us a shell, which includes all the build dependencies, but not the site
executable, thus allowing us to hack the generator and compile it manually, as well.
Here, we use nix shell
and nix develop
as short-hands for using the default package. nix shell .#ssg
and nix develop .#ssg
would have the same effect. We can also use nix build
to build the site generator executable.
If you are unfamiliar with Nix, what nix build
does is it builds the package in a separate part of the file system (called the Nix store) and puts a symlink result
in the current working directory. This symlink points to a directory which only contains the “final products” as specified by the outputs
of the package definition.
If you are familiar with Nix, you will probably want to ignore this oversimplified explanation.
This blog is currently hosted on Codeberg Pages. This means, there is a git repository containing the static content; this one to be exact.
There are ways of keeping everything (the generator, markdown, and html) in the same repository. It usually involves keeping the generator and markdown in a subdirectory, which is what I was doing when using Pelican; but this approach never really clicked with me and always felt a little hack-y. So now, I keep the generator separate and put only the stuff in the pages
repository that is actually to be hosted, since that is its whole purpose. All we need to do is to copy the content of the _site
directory (produced by site build
) to the pages
repository and push our changes.
This workflow could likely be automated with CI steps and all the bells and whistles, but with the current posting frequency of this blog, it doesn’t seem worth the effort.
Final Thoughts
All in all, the switch to Hakyll has been a very pleasant experience. The DSL is fun to write and powerful enough to do everything I need. I also enjoy the declarative nature of it. Finally, it gives you a better understanding of what goes into a static site generator, what is needed to generate an RSS feed or a site map, for example. It’s always good to know a little more about how things work, even if you do end up using a framework that does it for you.