As someone on the Internet said not so far ago. Building its own static building system is a rite of passage for many developers. It has a lot of nice features. It gives a goal with a feeling of accomplishment. It is simple enough so most developers could build their own system. It could also become complex when you go down the rabbit hole.
Along the years I used different tools and used and wrote of few static website systems:
- nanoc (in Ruby), at that time it looked like this: old nanoc 2 website
- hakyll (haskell static website generator)
- org-publish (emacs package in conjunction with org-mode)
- shake (haskell again)
So if you look at the progression, I first used nanoc because I used ruby and it was a new solution, the website looked really great. Also the main developer Denis Defreyne was really helpful. Ruby was really great at dealing with regular expressions for hacking my documents.
Then I was interested in Haskell, and I switched to a Haskell-made solution. I used hakyll, and I wrote a bit about it in Hakyll Setup. As a side note, the author of Hakyll Jasper Van der Jeugt is apparently a friend of the author of nanoc. They both wrote a static site generators with their preferred programming language. I added a lot of personal features to my own site builder. It was a nice toy project.
Then, due to a major disruption in my professional and private life I stopped to take care of my website.
And a few years ago, I wanted to start a new website from scratch. In the meantime I switched my editor of choice from vim to Emacs. I started to work in Clojure and emacs is generally a natural choice because you can configure it with LISP. I discovered org-mode (I don't think the homepage of org mode makes justice to how incredible it is). So org-mode comes with an export system. Thus I switched to org-publish. Again I wrote a bit about it.
It was nice but slow. I improved a few things like writing a short script to Generate RSS from a tree of html files. I still had the feeling it was too slow.
Static site building is a specific usage of a build system. And as I
knew I could use pandoc
to build HTML out
of org-mode files and still versed in the Haskell culture I decided to
try shake. You can learn more by
reading this excellent paper about it, I think all developer should read
it: Build
System à la carte.
As a bonus, pandoc is written in Haskell. I could then directly use the pandoc library in my build program. It worked like a charm and it was very fast as compared to other solutions I tried. So really let me tell you shake is a great build system.
Unfortunately it was not perfect. While it was very fast, and I was able to use pandoc API directly. It made me dependent on Haskell. The best way I found to have Haskell reproducible build environment is to use nix. This was great until the Big Sur update. To keep it short, nix stopped working on my computers after I upgraded my to Big Sur. Gosh, it was painful to fix.
Concurrently I discovered gemini and wanted to duplicate my website into gemini sphere. So I tried to update my build system but my code was to oriented to use pandoc and it was painful to have gemini in the middle of it. Particularly, generating a gemini index file. My main goal was to have gemini file that could only be linked from withing gemini sphere. Because gemini is a lot smaller web where you could feel a bit more protected from what the Web has become along the years. Whatever, in the end, I just had two problems to tackles.
- Haskell became difficult to trust as very stable tool. Stable in the sense that I would not have any support work to do in order to keep just using it and not fixing/tweaking it.
- Simplify the overall system to have a simpler build description
So a very stable tool that I am pretty sure will still work almost
exactly as today in 10 years is make
(more precisely gnumake). I
expected a lot of people had already come to the same conclusion and
wrote about it. To my great surprise, I found very few article about
generating static website with make. I only found solutions a bit too
specific for my need. This is why I would like to give you a more
generic starting point solution.
The Makefile
Instead of copy/pasting my current Makefile
entirely let me give you a more generic
one. It should be a great start.
The first part will be used to simply copy the files from src/
to _site/
.
all: website
# directory containing my org files as well as my assets files
SRC_DIR ?= src
# directory where I will but the files for my website (HTML + assets)
DST_DIR ?= _site
# list all files in src
# if you want to exclude .org files use the exclude from the find command
SRC_RAW_FILES := $(shell find $(SRC_DIR) -type f)
# generate all file that should be copied in the site
# For my site, I want to publish my source files along the HTML files
DST_RAW_FILES := $(patsubst $(SRC_DIR)/%,$(DST_DIR)/%,$(SRC_RAW_FILES))
ALL += $(DST_RAW_FILES)
# COPY EVERYTHING (.org file included)
$(DST_DIR)/% : $(SRC_DIR)/%
"$(dir $@)"
mkdir -p "$<" "$@" cp
This part is about running the pandoc
command for all org
files in src/
so they generate a html file in _site/
.
# ORG -> HTML, If you prefer markdown replace .org by .md
EXT := .org
# all source file we'll pass to pandoc
SRC_PANDOC_FILES ?= $(shell find $(SRC_DIR) -type f -name "*$(EXT)")
# all destination files we expect (replace the extension by .html)
DST_PANDOC_FILES ?= $(subst $(EXT),.html, \
$(subst $(SRC_DIR),$(DST_DIR), \
$(SRC_PANDOC_FILES)))
ALL += $(DST_PANDOC_FILES)
# use a template (you should use one)
TEMPLATE ?= templates/post.html
# URL of the CSS put yours
CSS = /css/y.css
# The pandoc command to run to generate an html out of a source file
PANDOC := pandoc \
-c $(CSS) \
--template=$(TEMPLATE) \
--from org \
--to html5 \
--standalone
# Generate all html if the org file change or the template change
$(DST_DIR)/%.html: $(SRC_DIR)/%.org $(TEMPLATE)
$(dir $@)
mkdir -p $(PANDOC) $< \
$@
--output
A missing part is often the part where you would like to generate an index page to list the latest posts. Here you are a bit alone, you need to make one yourself. There is not generic way to do this one.
# Generating an index page is not difficult but not trivial either
HTML_INDEX := $(DST_DIR)/index.html
MKINDEX := engine/mk-index.sh
$(HTML_INDEX): $(DST_PANDOC_FILES) $(MKINDEX)
$(DST_DIR)
mkdir -p $(MKINDEX)
ALL += $(HTML_INDEX)
Finally, a few useful make commands. make clean
and make deploy
.
# make deploy will deploy the files to my website write your own script
deploy: $(ALL)
engine/deploy.sh
website: $(ALL)
.PHONY: clean
clean:
-rm -rf $(DST_DIR)/*
Limitation: make
is old. So it really
does not support spaces in filenames. Take care of that.
Let me tell you. While this is quite a minimalist approach (<100 lines) it is nevertheless very fast. It will only generate the minimal amount of work to generate your website. I have a nice watcher script that update the website every time I save a file. It is almost instantaneous.
The only risky dependencies for my website now is pandoc
. Perhaps, they will change how they
generate an HTML from the same org file in the future. I still use nix
to pin my pandoc version. The static site
builder itself is very simple, very stable and still very efficient.
As a conclusion, if you want to write your own static site builder that's great. There are plenty of things to learn along the way. Still if you want something stable for a long time, with a minimal amount of dependencies, I think this Makefile is really a great start.