John Hawthorn

This Website is a Makefile

To create a blog engine from scratch, you must first create the universe Carl Sagan

I’ve been through a few blog engines, but for the past few years I’ve been generating this blog from a Makefile.

Makefiles get a bad rap. They don’t have the most obvious syntax, but they can be quite flexible and simple.

To generate this website I use:

MARKDOWNFILES:=$(shell find src/ -type f -name '*.md') HTMLTARGETS:=$(MARKDOWNFILES:src/%.md=build/%/index.html) all: $(HTMLTARGETS) build/%/index.html: src/%.md @mkdir -p $(dir $@) bin/render $< > $@ clean: rm -Rf build

This tells make to find all markdown files, and for each one, generate build/BASENAME/index.html from each src/BASENAME.md calling bin/render.

I love make.

It requires some understanding of make’s magic variables and pattern rules which certainly aren’t the most transparent, but I’ve grown to like them. Kind of like git’s awkward usability.

After that, all that’s needed is the bin/render script to convert the markdown into HTML and throw it in a layout.

#!/usr/bin/env ruby require 'kramdown' body = Kramdown::Document.new(ARGF.read).to_html puts(DATA.read % {body: body}) __END__ <!DOCTYPE html> <html> <head> <!-- stuff --> </head> <body> <article> %{body} </article> </body> </html>

Regenerating the entire site with this file (make clean all) is a bit slower than it would be under nanoc or jekyll since it needs to re-invoke ruby for each file. But incremental builds, running make with only one source file changed, is super fast: it just regenerates the single output file it needs to. make knows that the output html files are based on a similarly named markdown. It compares the files’ mtimes to determine what needs to be built.

I use this with Gary Bernhardt’s serveit, a static web server which can be configured to run a command before serving each request.

bundle exec bin/serveit -s build make

By using make, I’ve written my own blog engine “from scratch” (other than the markdown renderer, etc) without having to build or even think about a dependency graph.

Additional thoughts