A 450 KB static site generator based on Markdown and Lua

This page is copied from the blog of Jared Krinke, the creator of Luasmith

Today, I unveil my most gratuitous static site generator yet: luasmith.

Show me the code!

It's like Metalsmith in Lua. See the tutorial for more, but here's an example that converts Markdown to HTML and adds the page's title to the resulting HTML:

-- Minimal HTML template (used below)
local outer = [[
<html>
  <head><title><%= title %></title></head>
  <body><%- content %></body>
</html>
]]

-- Read content/*.md, convert to HTML,
-- apply template, write to out/*.html
return {
  readFromSource("content"),
  processMarkdown(),
  applyTemplates({ { "%.html$", outer } }),
  writeToDestination("out"),
}

Note: templates use a Lua-native templating language called etlua and file matching uses Lua patterns.

Motivation

Obviously, the world does not need another static site generator. So how did I end up here?

In a word, my motivation was: simplicity. I wanted an SSG that was:

Implementation

Here's my take on the goals above.

Simple to understand

First, I wanted the architecture to be simple, both in design and use:

Instead of creating a bespoke domain-specific language for defining the structure of a generated site or its templates, you just write some Lua code that glues together a few processing nodes and then supply templates that also use Lua (e.g. for iteration).

Overall, I'd describe the architecture as "minimalist Metalsmith in Lua, with zero runtime dependencies".

Simple to bootstrap

Given my musings about future-proof programming languages, it's obvious that I'd like to be able to compile and use my software in the future. That is easier said than done! The problem is that identifying languages that will stick around is hard. Twenty years ago, Perl might have been a reasonable choice for an SSG, but today I don't even remember how to install Perl modules.

The (previous) best static site generator ever made is built on TypeScript/JavaScript and Deno. As much as I like Deno, I'm not confident it will be maintained decades down the road. Given that I'll never know how to get it running on an i586 computer, I have doubts I'd be able to get it running on potential future architectures (RISC-V?) either.

To avoid these headaches, I went with the lowest common denominator: C. It's not convenient, it's not modern, but C works everywhere and I'm certain C will persist (for better or worse).

Aside: ideally, I'd be writing as much native code in Rust as possible, to ensure memory safety. Unfortunately, Rust's modern approach is at odds with my desire for simplicity. The toolchain is huge, the dependency trees are large, and the language is vast. Rust definitely looks like the future of robust low-level software, but for cozy side projects, I prefer being a simpleton living in the past.

Simple to maintain

One frustration I have with the JavaScript ecosystem is that it's constantly changing. Node, Deno, and Bun do a respectable job of keeping old versions around, but I don't want to have to worry about breaking changes.

On the other hand, C changes very slowly, and previous versions of Lua are essentially set in stone. Throw in some static linking, and you've even got an artifact that should stay usable for a long time.

I've also minimized the number of (vendored, compile-time) dependencies involved. Here's the full list:

You'll note that these libraries are doing all of the heavy lifting. I've basically only written glue code, an entry point, and some infrastructure. And most of the code I wrote is in Lua (which is much easier to write than C--and fast enough for all but the innermost loops).

Simple to deploy

Static binaries are wonderful. Tiny static binaries are even wonderful-er. Just copy over a tiny zip file, unzip it, and you're done. Need I say more?

Downsides

Of course, there are downsides to the approach I took:

Additionally, I haven't taken the time to set up a proper development and debugging environment for C and Lua. I need to investigate static analysis and debugging tools for Lua, as well as find a tolerable frontend for GDB. This is where I really miss Emacs+SLIME for Common Lisp or VS Code for TypeScript/Python.

Future areas of exploration

Now that I've got a static site generator running on a vintage laptop with NetBSD, where am I headed next? I'm not exactly sure, but some ideas follow.

Designing for the text-mode web

At some point, I'd like to redesign my site using an even more minimal theme. In fact, I'd like to optimize my site for text mode browsers like lynx and w3m. Why? Because I like using w3m and I want my site to be easy to use within w3m. Or maybe it's because I hate how bloated modern web browsers have become.

Further simplifying distribution

Distributing native code necessarily requires per-platform packages. Or does it? Can I package and release this minimal static site generator as a multi-OS polyglot binary using Cosmopolitan libc? Update: this now exists, but sadly, it is 64-bit only.

Simplifying the entire system

I'd like to see if I can bootstrap my entire web site's workflow from Oasis Linux (a small, statically linked, Linux-based operating system that is simple, but capable). Oasis sounds like a modern system that a single person can wrap their head around (minus the Linux kernel--though perhaps a simpler kernel could be substituted in the future?).

Blogging on vintage computers

I'm curious how far back I can go as far as vintage computing and still be able to build a static site. Can I build my SSG on Windows 98? DOS? Amiga? Inquiring minds want to know!

Conclusion

Creating a non-bloated Markdown-based static site generator has been a bucket list item for me--and now it's done!

Beyond personal goals, I found C+Lua to be a comfortable combination for side projects. This came as a surprise! Lua isn't my favorite language to write (though it's certainly much simpler to wield than C). Having said that, it's a beautifully simple language that's easy to integrate. Despite being primarily driven to Lua by my goal of building a small (in binary size) tool, Lua's minimalist take on a mostly-normal-looking scripting language won me over because I could literally pick it up and be productive within an hour or two.

With that out of the way, I should probably attend to hobbies other than static site generator performance art. Until next time!

Resources

Note: the example repository contains a simple self-contained luasmith pipeline for generating its README file.