How to roll your own Blog Engine

This Blog doesn't use any Frameworks or Plugins, or Javascript of any kind. There's very little css and the whole thing should fit in just a couple of kilobytes.

Hopefully that results in speedy page loading and great performance regardless of the device used to read it.

However as I'm not interested in manually writing HTML documents and upload them via FTP to a webserver as in ye olden times I decided to write a couple lines of code to fully automate the process and integrate it with my general notetaking workflow.

This post is intended as a guide to everyone who wants to do the same (or is simply interested in the inner workings of this blog), as well as serve as documentation for my future self, so that I can look it up when I inevitably forget what I did.

Writing

HTML is a very fine markup language. But it doesn't offer a super great writing experience for long format text. Great for building websites, not so great for formatting text manually.

Instead I write all my Notes in markdown. That's not a new idea, many people have adopted markdown as their note taking format of choice. And not without reason. Markdown is very easy to write and read, offers great out of the box formatting and there's a great eco-system of plugins around it that can extend its functionality to include support for charts, graphs, mathematic typesetting and many other things. As an added bonus it's a raw text format, and so it will forever remain readable even if we were to somehow lose access to all markdown parsers.

I use NeoVim to edit all my files and I've set it up to offer a very comfortable writing experience with spellchecking, note linking, live preview and many other creature comforts. However that's a topic for another day. Any Editor will work for this.

Publishing

Once I'm ready to release my ramblings into the internet I need to somehow convert my meticulously written markdown file into html and link it on the front page of my Blog.

I've written a small js script that does this for me. Luckily I didn't need to write my own markdown parser (Although people have done this), but can instead use the excellent markdown-it.

This does all the heavy lifting for me. I just need to read the markdown file, pass it to markdown-it and out comes a perfectly formatted html string.

There are a few more things left to be done, as this does not contain any header information (like a title), nor any styling. So I've written a small html template that looks like this:

<html>
<head>
	<title>${0}</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link href="./../css/style.css" type="text/css" rel="stylesheet">
	<link href="./../css/syntax.min.css" type="text/css" rel="stylesheet">
</head>
<body>
	${1}
</body>
</html>

The custom stylesheet responsible for all formatting is very simple and looks like this:

@font-face {
  font-family: lora;
  src: url("../fonts/Lora-VariableFont_wght.ttf");
}

html {
  max-width: 70ch;
  padding: 3em 1em;
  margin: auto;
  line-height: 1.75;
  font-size: 1.25em;
  font-family: lora, sans-serif;
}

pre {
  -moz-tab-size: 2;
  tab-size: 2;
  line-height: 1.25;
}

That's it. Most of it is an exact copy of this and it works incredibly well.

I serve a custom font called Lora which I enjoy looking at more than any of the default fonts. It's by far the largest part of the entire website, but considering that this article (without any caching) still comes in at <250 kilobytes I think that's acceptable.

There's also a little bit of css used for syntax highlighting, curtesy of hightlight.js. I intend on only importing syntax highlighting functionality on pages that actually make use of it, but for now it's included by default.

The script then simply reads the template and regex replaces ${1} with the output from markdown-it. The title is supplied as a command line argument.

All in all the complete script looks like this:

#!/usr/bin/env node
var fs = require("fs");
const hljs = require("highlight.js");
const { exit } = require("process");
const yargs = require("yargs");
const md = require("markdown-it")().use(require("markdown-it-highlightjs"), {
  highlight: function (str, lang) {
    console.log(lang);
    if (lang && hljs.getLanguage(lang)) {
      try {
        return (
          '<pre class="hljs"><code>' +
          hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
          "</code></pre>"
        );
      } catch (__) {}
    }

    return (
      '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + "</code></pre>"
    );
  },
});

const argv = yargs(process.argv.slice(2)).argv;

if (!argv.i) {
  console.error("No input file provided");
  exit();
}
if (!argv.o) {
  console.error("No output folder provided");
  exit();
}
if (!argv.t) {
  console.error("No title provided");
  exit();
}

var template = fs
  .readFileSync(
    "/Users/tjennerjahn/Programming/Projects/NoteToPost/template.html"
  )
  .toString();

var note = fs.readFileSync(argv.i).toString();
var body = md.render(note);
template = template.replace("${0}", argv.t);
template = template.replace("${1}", body);
fs.writeFileSync(`${argv.o}${argv.t.replaceAll(" ", "_")}.html`, template);

I've hardcoded the filepath of the template because this only needs to work for me and isn't intended as a general purpose tool, but I'm pretty sure that I'll change that once I move the project once and have to figure out why everything stopped working.

Something to note is the first line:

#!/usr/bin/env node

This provides an environment for your terminal so that the file can be executed directly. No need to run node somefile.js. To make it globally accessible, I add this to my package.json:

{
	"bin": {
		"notetopost": "./index.js"
	}
}

Afterwards I run npm link inside the project folder and VoilĂ ! notetopost is a globally accessible command that I can run in my terminal from anywhere.

I've created a macro in vim that automatically runs this on the current buffer, I only need to fill in a title and the rest happens on its own.

Hosting

After running the script, the generated .html file sits inside the essays directory that's part of a git repo that I use to manage this blog. I manually add the entry to the front page and push a new commit.

Github does the rest for me. This Blog is hosted on GitHub Pages and the site gets updated automatically when I push to the repository.

I have no problem relying on them to host this site, since switching to something else would be incredibly easy. If they ever shut down Github Pages I can simply point to another service, or even host an apache or nginx server. Since all of this is just html and css without any integrations into some specific service there's nothing that would make a switch difficult.

So until that happens I can enjoy their free website hosting that does everything for me.