This blog post explains how to setup this site. You can find the source code on GitHub. We will not publish yet, it will be all local. Publishing is for the following post.
The stack we’ll be using is Hakyll on top of Haskell and Nix. I use Emacs as my editor so there will be some later blog posts explaining how to set that up.
We will start by writing Nix files and for learning Nix, I recommend the Nix pills. I thought I could simply find example snippets online and learn from that but the language and the conventions were too alien for me to understand anything.
I followed this blog
post
from Robert Pearce to set things up. I removed the niv
part as I am
not using it.
There was an encoding bug as I’m using UTF-8 but by default Hakyll only understands ASCII encoding. To solve that one, I followed this blog post from Ondřej Slámečka. There is a fix in the Hakyll’s FAQ, but the post from Ondřej is tailored for nix so I followed it.
I recommend reading both blog posts as they explain things well. That said, here is my version.
Initial Nix Files
Corresponds to this commit.
There are 3 nix files to create, then we will be able to use
hakyll-init
to generate the base site.
./default.nix
(import ./release.nix { }).project
./shell.nix
(import ./release.nix { }).shell
./release.nix
# https://robertwpearce.com/hakyll-pt-6-pure-builds-with-nix.html
{ compiler ? "ghc883"
, pkgs ? import <nixpkgs> {}
}:
let
inherit (pkgs.lib.trivial) flip pipe;
inherit (pkgs.haskell.lib) appendConfigureFlags;
haskellPackages = pkgs.haskell.packages.${compiler}.override {
overrides = hpNew: hpOld: {
hakyll =
pipe
hpOld.hakyll[ (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
];
# https://www.slamecka.cz/posts/2020-06-08-encoding-issues-with-nix-hakyll/
hakyll-blog = (hpNew.callCabal2nix "blog" ./. { }).overrideAttrs (old: {
nativeBuildInputs = old.nativeBuildInputs or [] ++ [
pkgs.makeWrapper];
postInstall = old.postInstall or "" + ''
wrapProgram $out/bin/site \
--set LANG "en_US.UTF-8" \
--set LOCALE_ARCHIVE "${pkgs.glibcLocales}/lib/locale/locale-archive"
'';
});
};
};
project = haskellPackages.hakyll-blog;
in
{
project = project;
shell = haskellPackages.shellFor {
packages = p: with p; [
project];
buildInputs = with haskellPackages; [
ghcid
ghcide
brittany
hlint];
withHoogle = true;
};
}
The first two files are using the release.nix
one so let’s dive into
that one.
First, we set some defaults and import some functions:
{ compiler ? "ghc883"
, pkgs ? import <nixpkgs> {}
}:
let
inherit (pkgs.lib.trivial) flip pipe;
inherit (pkgs.haskell.lib) appendConfigureFlags;
Then we set some overrides. To know what an override is, check the Nix pills.
{compiler}.override {
haskellPackages = pkgs.haskell.packages.$overrides = hpNew: hpOld: {
First for Hakyll, we configure it with watchServer
and
previewServer
options. Both options are super useful when developing
as the produced ./result/bin/site
executable will watch for file
changes and rebuild the blog post when needed.
hakyll =
pipe
hpOld.hakyll[ (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
];
Second, we create a variable hakyll-blog
to compile the blog. The
name "blog"
must correspond to the name of the cabal file, which we
will generate later. We also set some flags to make Hakyll able to
read UTF-8 files.
-blog = (hpNew.callCabal2nix "blog" ./. { }).overrideAttrs (old: {
hakyllnativeBuildInputs = old.nativeBuildInputs or [] ++ [
pkgs.makeWrapper];
postInstall = old.postInstall or "" + ''
wrapProgram $out/bin/site \
--set LANG "en_US.UTF-8" \
--set LOCALE_ARCHIVE "${pkgs.glibcLocales}/lib/locale/locale-archive"
'';
});
We then set the project
variable used in the default.nix
file:
-blog;
project = haskellPackages.hakyll
in{
project = project;
Finally, we set the shell
variable and provide some packages useful
for editors, namely ghcide
, brittany
, hlint
and a local hoogle
with withHoogle
:
{
shell = haskellPackages.shellFor packages = p: with p; [
project];
buildInputs = with haskellPackages; [
ghcid
ghcide
brittany
hlint];
withHoogle = true;
};
Generate the Initial Template Blog
Corresponds to this commit.
Next step will take some time because we will run Nix for the first time. It will fetch and compile every dependencies. It took roughly 1 hour on my laptop.
$ nix-shell --pure -p haskellPackages.hakyll --run "hakyll-init ."
This will generate a cabal file named after the git repo’s directory. Here is an example, with the following directory structure:
./blog
./blog/.git
Running the hakyll-init .
command in the blog
directory will create the following files:
[nix-shell:~/blog]$ hakyll-init .
Creating ./posts/2015-11-28-carpe-diem.markdown
Creating ./posts/2015-10-07-rosa-rosa-rosam.markdown
Creating ./posts/2015-12-07-tu-quoque.markdown
Creating ./posts/2015-08-12-spqr.markdown
Creating ./site.hs
Creating ./images/haskell-logo.png
Creating ./templates/post-list.html
Creating ./templates/default.html
Creating ./templates/archive.html
Creating ./templates/post.html
Creating ./css/default.css
Creating ./index.html
Creating ./about.rst
Creating ./contact.markdown
Creating ./blog.cabal
You get a bunch of files and 4 example blog posts under the posts/
directory. Also, the Cabal file blog.cabal
is named from the
directory and must be named that way for the release.nix
file to
work correctly, as we established previously.
Then, we can build the ./result/bin/site
blog executable:
$ nix-build --show-trace
Finally, we can run the executable to compile the site, including all 4 example blog posts:
./result/bin/site build
This will create the _cache
and _site
directories. The latter is
where the generated files will be located.
Serve and Watch For Changes
./result/bin/site watch
Your blog will be up and running at http://localhost:8000.
You can change posts, add or remove posts, change the css and other
files and the site
executable will see those changes and rebuild the
site. You just need to reload the site in your browser.
Gitignore
Corresponds to this commit.
I can’t live with temporary and generated files like that. Let’s add a
.gitignore
:
*#
*~
_cache/
_site/
result
The first two lines are for Emacs’ temporary files, you could omit them.
Your First Post
I just deleted all 4 example blog posts and created another file.
Conclusion
That’s it, we created our blog with Hakyll and Nix and we can see it locally. Next up, let’s publish it.