A picture of a chill skeleton figurine sitting in front of a computer.

Oops, I did it again or How I learned to stop worrying and love HTML

written on February 22, 2025

Header image by Oussama Bergaoui (Pexels)

When I wrote my first static site generator (SSG)—Flores—I thought it was all over, that I could kick back and relax, effortlessly edit my site and publish blogposts for the foreseeable future.

Unfortunately, that was far from the truth. As of today, Flores hasn't seen a new commit in 2 years, and my site is now being built with a different SSG. What happened?

Too much complexity (still)

In the blogpost I wrote about Flores, I explained that the biggest reason why I wanted to roll my own SSG in the first place was to get rid of unnecessary complexity. My site is very simple (and I'd like to keep it that way), so it was very frustrating to have to deal with Gem dependency issues back when I was using Jekyll.

So, the core motivation was to build a solid SSG that took care of all of the common-sense stuff (regarding the needs of most personal/blog sites such as this one): HTML templating (with like, full introspection of the data of the page), Markdown-based pages (compiled to HTML), code highlighting, optimization of image assets.

From the user's side, there is not much to do. From the developer's side, managing the whole Markdown-to-HTML chain was kind of a mess. As I said on that blogpost, the markup generated by the Python libraries left a lot to be desired. Simultaneously, the code highlighting code was unnecessarily complex, and some stuff like auto ID-tagging headings to make them easily linkable was not taken care of at all, which means you had to write cursed templating code to modify the HTML elements during generation.

The templating itself was done through Jinja. Jinja is great, but this is very much a just because you can doesn't mean you should moment. I got up to all sorts of tomfoolery just because I had the ability to do so:


{% set content_ns = namespace(content_lines=[]) %}
{% for line in page.content.split("\n") %}
    <!-- Careful to only pick up headers (and not stuff like `<hr>`). -->
    {% if (line | length >= 4) and line[1:3] in ("h1", "h2", "h3", "h4", "h5", "h6") %}
        {% set header_size = line[1:3] %}
        {% set header_title = line[4:line.index("</h")] %}
        {% set header_id = header_title.lower().replace(" ", "-") | e | urlencode %}
        {%
            set header_with_anchor = '<a href="#%s">%s</a>' | format(
                header_id, header_title
            )
        %}
        {%
            set header_with_id = '<%s id="%s">%s</%s>' | format(
                header_size, header_id, header_with_anchor, header_size
            )
        %}
        {%
            set content_ns.content_lines = (
                content_ns.content_lines + [header_with_id]
            )
        %}
    {% else %}
        {% set content_ns.content_lines = content_ns.content_lines + [line] %}
    {% endif %}
{% endfor %}

{{ "\n".join(content_ns.content_lines) }}

Falling out of love

The other big reason was falling out of love with Python. For a long time, it was my language of preference when it comes to small CLI programs. I had quite a bit of experience writing Python in all sorts of contexts (personal, academic, professional) and, at the time, it was relatively easy to share your Python software with other people. Easy as pip install my-tool. Have you seen the Python package management ecosystem lately?

The “hey X how's it goin” meme, but it's about Python. On the second square, we see a `pip install flores` command, followed by long, verbose output explaining why `flores` must be installed in a virtual environment in order to work. The comic character just stares at the output and goes “yea”.

On top of that, I'm no longer convinced that the templating and Markdown-based pages are even worth it for a site this size. Sure, I could write very intricate template code, such that I only had to specify the HTML once, and then just inject data into it. For example, I have lots of lists on this site (teaching activities, talks, projects and so on). Using the template engine, I was able to write HTML to handle any and all of these lists, and I could just use it instead of having to rewrite it for each one.

But at the end of the day... is that really less complex? If you have dozens of lists, some with hundreds of elements, then yeah, sure. But my lists are not—and never will be—that long. The trade-off here is that HTML is supposedly tedious to write, and that Markdown plus templating will simplify it. But, I still have to do this little dance to get figures to display as they should:


![Default tmux status bar](/assets/images/blog/tmux-default-status-bar.png)
{ .figure-image }

The default tmux look (status bar at the bottom).
{ .figure-caption }

Is that really more convenient? Frankly, the HTML equivalent is not even that much more complex, and to be honest it's much easier to memorize. Plus I can use proper <figure> and <figcaption> elements, and style them directly, as opposed to using classes on random <p> elements.


<figure>
    <img
        alt="Default tmux status bar"
        src="/assets/images/blog/tmux-default-status-bar.png"
    >
    <figcaption>The default tmux look (status bar at the bottom).</figcaption>
</figure>

Besides, I had a lot of dancing around markup issues to do due to the weird code highlighting that was done through Markdown. From the old SCSS code:


// There's a surprising amount of fonts to reset to make sure listings behave
// correctly!
td.code, td.linenos, pre, code {
    font-family: $code_font;

    // Fix bizarre bug in Safari (on iOS) when the font size will randomly explode.
    -webkit-text-size-adjust: none;
}

Honestly, it has been much easier to style when I know what the actual HTML I'm styling is.

Hello, Mr. Bones

So what would I rather use then? Should I just write every page manually in HTML?

Well, no, that's annoying, for several reasons:

  1. There is some shared stuff between multiple pages. Duplicating it across all pages would make changing it a pain. My site is not big, but there are more than 50 separate HTML files in the generated site (also see (2) below).
  2. For convenience reasons, it's nice to create page copies in some cases. For instance, the URL of this page is /2025/02/22/mrbones. That's not an HTML file, so how come it works? Well, the trick is to generate both /2025/02/22/mrbones.html and /2025/02/22/mrbones/index.html, which makes it work with or without the .html suffix.
  3. It's nice to be able to separate sections of pages into different files, for ease of maintenance. There is no native way of doing this in pure HTML. You can definitely write some JavaScript to do it, but I don't like writing JavaScript if I don't have to. Plus, that means that it would happen dynamically—which is its own can of worms—or else you're really just making an SSG in JavaScript, which, yikes.
  4. It's nice to have permalinks. For example, instead of actually creating /YYYY/MM/DD/post-title.html (including the nested directories), it would be nice to just have a /YYYY-MM-DD-post-title.html file that the SSG knows where to put in the generated site.

This made me think that all I need is essentially some extremely simple templating (some way of including HTML files in other HTML files, mostly), permalinks, plus the page copying trick to have better URL support.

Now, what tech stack would be very easy to install and run for me? Python's definitely out of the question. I like Rust these days, but that sounds like way too much, surely this is just a couple sed commands chained together... and that's how mrbones was born.

Mrbones is a single Bash script. You don't even have to install it, let alone deal with dependencies or breaking system packages or whatnot. It essentially only depends on basic stuff that comes with your Linux, like sed. It's also much faster than, say, Flores. But can it run on Windows? Honestly, I don't know, but I don't ever really need to build my site on Windows.

A quick comparison

With Flores, my site's source code looked like this:


_assets/
_css/
_data/
_drafts/
_pages/
_posts/
_templates/
LICENSE
README.md

Now, it looks more like this:


404.html
_templates/
assets/
blog.html
contact.html
css/
index.html
js/
posts/
projects.html
thanks.html

Essentially, more stuff has been moved out of _templates and _data and into simple HTML files.

File type Files in Flores source Files in mrbones source
Page (Markdown/HTML) 28 (6671 lines) 26 (9635 lines)
Template (HTML with/without templating) 8 15
JSON 10 0
SCSS/CSS 8 9

Also, the generated HTML pages seem to shrink a little, although part of that is because I removed categories and tags from the blog:

Metric Flores-generated site mrbones-generated site
HTML files 54 51
HTML LOC 84591 23072
Build time (seconds) 5 2

So, is mrbones better? Should you use it? I don't know. You can. Personally, it has taken a load off my mind, and it seems to be encouraging me to go easy on the templating. I feel like I have a better handle on the site's layout now, and honestly, for the first time in a while, writing a blogpost felt easy. So I'll stick with mrbones, but who knows, I hear Typst is going to support HTML soon...