Roland is a Static Website Generator Written in Swift

If there's one thing I'm good at, it's reinventing wheels. So here's Roland - an open source, blog-aware, static website generator written in Swift that also uses PHP under the hood because PHP is still the best template language.

You're probably thinking to yourself

Oh, god. Why?

You're probably also asking

Isn't "blog-aware, static website generator" literally the purpose of Jekyll?

I'll answer those questions and many more below. But first, here's why I moved this blog off WordPress for the first time in thirteen years. (Other than two months last Summer when I experimented with Ghost.)

Why leave WordPress?

First of all, WordPress is great. If you want to build a personal website - or maybe even a company website - just use it. It's a capable, featureful, well-maintained, open source success story that is stewarded by an amazing team. But I kept running up against two problems.

  1. Control
  2. Performance

I'm an old-school web nerd who likes to maintain complete control over my website - from the design down to the raw HTML. And despite twenty years of experience with PHP, every time I looked under-the-hood to see how WordPress worked or to tinker with a plugin, the coding style and way things were organized gave me hives. (That's not meant to be real criticism. What Automattic has built is awesome. I'll certainly never create anything that rivals WordPress.) It's just to say that years ago I gave up attempting to work within the lower layers of their framework and turned to using plugins for everything.

Of course plugins present their own problems - namely, I'd lose control over the HTML being generated and when and where all the various scripts and static assets were injected. It just made everything a mess. Sure, for a while I used a completely custom template that prevented plugins from doing anything on their own unless I allowed it, but then that defeats the purpose of using plugins at all.

And then the more plugins you install, the slower WordPress becomes - and it's already not that fast to begin with. For ten years, this blog was hosted on the same VPS as my business website and mostly worked fine. But every now and then I'd get a spike in traffic and the server would fall over. (For reference: a 4GB / 2 CPU VPS with Linode.)

I'd take the necessary precautions like using WP Super Cache, keeping all static assets offsite on a CDN, and an unexpectedly popular blog post would still bring things crashing down. If it were just this dumb blog, I wouldn't care. But it would also take down my online store with it, which costs me money and annoys my customers.

So a year ago I moved this blog to its own DigitalOcean droplet and even took the time to (finally) setup Varnish in front of it. It's been great having it isolated from all of my other web properties, but a Hacker News or Daring Fireball link will still cripple it. The only thing that has truly worked for those traffic spikes has been to also put Cloudflare in front. And that's great, kudos to them for the amazing free service, but that means holy crap I've got three caching layers (not counting my CDN) where new posts, edits to existing posts, and template changes can get hung up.

Yes, yes. The droplet this blog is (was) running on was DigitalOcean's lowest-tier $5/month box, so I'm well aware of what I was paying for. But I'm not willing to pay (a lot) more just to support an occasional burst in traffic - at least not when I think there are other ways to find a solution. There are some wonderful, dedicated WordPress hosts out there that I've worked with for client websites in the past. But even with WP Engine, I would have blown past their $115/month plan five months in the last calendar year.

So the idea of moving to a static website that can withstand bursts of traffic has been percolating in the back of my head for a few months now.

Looking for a static website generator

My first choice was Jekyll, which has been the basis of my company website for a decade. And it's fine. But...

  1. When I rebuilt my company website with Jekyll in 2011, I couldn't find a way to make it feel like a real, traditional blog. So I've been stuck with this awful blog overview page for nine years. Things are probably better now, but I haven't looked closely at the project in a long time to find out.
  2. I'm perpetually afraid to touch any of the software on my server for fear of breaking Jekyll's dependencies. I'm sure it was my fault, but at one point I was unable to do a build of my company website after an unattended Ubuntu security update broke something related to Jekyll and Redcarpet and Kramdown. It took me two weeks of off an on tinkering before I could get back to a working state.
  3. Jekyll is written in Ruby, which is so, so far removed from the ecosystems I normally work in that I don't even know where to start when something goes wrong.

So I crossed Jekyll off my list. The next obvious choice seemed to be Hugo. Everyone who has ever mentioned Hugo to me has raved about how wonderful it is. And my surface-level investigations into the framework seemed to confirm that. But, again, like Ruby, Go is not a native language for me. And switching would also mean having to learn an entirely new template system as well.

What about Swift?

But then I remembered reading about Publish, the static site generator that John Sundell wrote for his own website using Swift.

And, hey! I know Swift (mostly). That solves concern #3 above. And being a Mac and iOS developer, I feel pretty confident about being able to resolve any build or dependency issues that come up, so that fixes #2 as well. I guess that means I just need to look into how easily I can use Publish to generate the traditional blog-style website I'm going for.

But, first. What is a traditional blog?

For me, that means the type of personal websites (née web logs) that I came of age reading in late high school and early college. A home page with a reverse-chronological list of entries that you can then click through to read the author's post history. Every post can also appear within dedicated category archives and date-based archives (typically by month).

It's a simple, classic structure geared towards content that requires more attention than a tweet.

And reading the Publish documentation and examples leads me to think it would be possible to build. But then I got to the section on theming, which uses Plot.

Plot enables you to write HTML using native, fully compiled Swift code, by modeling the HTML5 standard’s various elements as Swift APIs. The result is a very lightweight DSL that lets you build complete web pages in a highly expressive way.

The first example on the Plot project page is:

let html = HTML(
    .head(
        .title("My website"),
        .stylesheet("styles.css")
    ),
    .body(
        .div(
            .h1("My website"),
            .p("Writing HTML in Swift is pretty great!")
        )
    )
)

Technically impressive? Absolutely. Type-safe? Both intriguing and appealing. But just, no. Gonna nope right out of there.

I love the foolishness, malleability, hackability, and pragmatism of HTML. And hats off to John if he can make a framework like Plot work for him - maybe I'm just not taking enough time to fully understand the benefits - but I ran screaming when I saw that's how you theme your website in Publish.

Wheel, meet your new inventor

But the idea of using Swift as the engine of the static generator - if not the templating language - really piqued my interest. So, over the course of a long weekend, fueled by a healthy dose of not invented here syndrome, I decided to take a stab at writing my own system. My calculus being

If I can knock this out in a weekend, that will be faster than the time it would otherwise take to learn the ins-and-outs of another framework. And while whatever I come up with is guaranteed to not be as robust or flexible as a Hugo or Jekyll, it is guaranteed to meet my needs without compromising on the design or structure of the website.

But let's be honest. It also sounds like a whole lot of fun for an old web nerd like myself.

So, that's why I built Roland. And if you're reading this post, that means the DNS switch was successful and everything you see was generated by the roland command line tool, dumped into git, and sent to Netlify for static hosting - and sped up by the amazing folks at BunnyCDN.

Project Details

My goals for Roland were:

  1. One command to run against a folder of web assets and build a website - just like Jekyll.
  2. Fast enough to be useable during a typical edit → compile → test workflow session.
  3. Clear separation between the Swift business logic that dictates the site's structure and the layout of the HTML.
  4. A flexible templating system.

Simple

Unlike Sundell's Publish project, I didn't want every website I build with my framework to be tightly coupled with the Swift code that generates the HTML output. His is a unique approach, but not for me. I like the Jekyll method, which is a simple command line tool you pass a folder of source files (Markdown, HTML, images, other static assets) and it outputs your generated web site.

Performance

One of Hugo's biggest selling points is how damn fast it can build your website. Their website claims, on average, < 1ms per generated page. I have no hopes of achieving compilation times like that. Instead, I just want my tool to be performant enough to where I don't hate my life every time I make a copy change or tweak my website's theme.

This blog has over 200 posts - written in Markdown. And then when you add in pages for categories, monthly archives, and other static pages, it totals closer to 400 pages that need to be generated along with all of the other static website assets and over a decade's worth of wp-content uploads. Here's how a full build works on my laptop:

36 seconds.

Yes, that's definitely not fast. It's on the order of 10 pages/second. I know Hugo would be way more performant. I don't know for sure how it compares to Jekyll, but clickontyler.com - with considerably less pages to build - still takes a full 9 seconds. So I feel like 36 seconds for a website of this size is likely in a similar ballpark.

(Roland was written in a mad dash over three days, so I know there are many places that can be optimized. Again, my initial goal was for it to be useable - optimizations can come later.)

That said, there's no way in hell I'd wait that long between every build when I'm iterating on basic HTML changes. So, roland has a number of command line options that lets you specify exactly which parts of your website to build or not build.

USAGE: roland [--config <file>] [--output <directory>] [--posts] [--pages] [--home] [--dates] [--categories] [--rss] [--no-public] [--no-clean]

OPTIONS:
  -c, --config <file>     The build configuration .plist to use.
        If omitted, "config.plist" in the current directory will be used.
  -o, --output <directory>
                          The build output directory.
        If omitted, "_www" in the current directory will be used.
        If the output directory does not exist, it will be created.
  --posts                 Only build posts.
  --pages                 Only build pages.
  --home                  Only build home archives.
  --dates                 Only build date archives.
  --categories            Only build category archives.
  --rss                   Only build RSS feed.
  --no-public             Don't copy "_public" directory.
        If set, the contents of the "_public" directory will not be copied into the output directory.
  --no-clean              Don't clean the output directory.
        If set, the contents of the outpupt directory will not be deleted prior to building.
  -h, --help              Show help information.

(Hats off to the Swift team and their amazing command line argument parsing library.)

When I'm actively working on the site, I'll run

roland --pages

which offers the shortest build time for me at just under a second. I find that perfectly acceptable for how I work.

Familiar Project Structure

As for the raw structure of my website's source files, here's how Roland dictates things are arranged:

If you've used Jekyll, the above should be very familiar.

(I've posted a sample project based on this blog that you can browse the source of to see a real world implementation.)

The content of your website is written in Markdown, while the HTML templates are, uh, HTML.

Static, one-off web pages are placed in the _pages directory. The format of a page looks like:

title: About
path: about/index.html
---
[Tyler Hall](https://twitter.com/tylerhall) is a Mac, iOS, and (former) web developer from Nashville, TN.

After spending three years as an engineer working for Yahoo! in San Francisco, I'm now working on my own apps under my tiny software development company, [Click On Tyler](https://clickontyler.com). We make productivity software for web developers and designers. Our flagship apps, [VirtualHostX](https://clickontyler.com/virtualhostx/), [Hostbuddy](https://clickontyler.com/hostbuddy/), and [Hobo](https://clickontyler.com/hobo/), are loved by over fifty-thousand web developers.

You can stalk me further on [Twitter](https://twitter.com/tylerhall), [GitHub](https://github.com/tylerhall), and [LinkedIn](https://www.linkedin.com/in/tylerhall). Here's a small collection of my [past projects](/portfolio/) - both commercial and open source - as well as [what I'm currently working on](site_BaseURLnow/).

The front matter of the page is loosely based on what Jekyll does. The only required properties are title and path. You can add additional info (one per line) and those key value pairs will be made available to Swift and the template language for you to do something with.

Blog posts are similarly stored in _posts and look like:

title: MailMate
date: 2020-04-06 11:48:18
slug: mailmate
categories: macOS,Reviews,Apps,Favorite Things
---
MailMate is a glorious, configurable, ultimate-nerd-dream of an email client built just for macOS. I use it every day in conjunction with Fastmail and SaneBox to give me email super powers.

But the killer feature? It just fucking works.

And believe me. I’ve tried every single email client for Mac and iOS – paid apps, free apps, subscription apps, apps from small companies, and apps from giant corporations.

I don’t know what else to say except that I love this app so much. And unless I’m horribly mistaken and there is secretly a giant corporation hiding behind MailMate and slurping up all of my private data, MailMate is built by a single developer, which is even more awe inspiring.
---
It took me a few days longer than I had hoped to get started, but here's the first post in a new [Favorite Things](https://tyler.io/category/favorite-things/) category celebrating [#IndieSupportWeeks](https://mobile.twitter.com/search?q=(%23IndieSupportWeeks)). First up, is the incomparable [MailMate](https://freron.com) by [Benny Kjær Nielsen](https://freron.com/about/). It's "the email client for the rest of us".

The front matter requires a title, slug and date. (path is not a thing because it is automatically derived by your blog's naming structure as you'll see later.) categories are optional and are a comma delimited list. Category names may contain spaces and other special characters. Only commas are not allowed.

The next section (after the ---) is an optional post excerpt that may be used in your templates. If you don't want to provide an explicit excerpt, you can leave it blank.

Everything after the second --- is the post's body written in Markdown according to the CommonMark spec. (HTML is allowed to be interspersed within the Markdown.)

The contents of the _public directory will be copied verbatim into your chosen output folder. This is where you'd store your images, stylesheets, JavaScript, etc.

Flexible Templates

Finally, the HTML templates that are used to built your website are in the _templates folder.

I can hear what you're thinking.

PHP?

That's right. I realized early on that I'd need at least some minimal templating language to build the design I wanted. I couldn't (didn't want to) put all that logic in Swift. I certainly had no intentions of writing my own templating language, so I turned to Google and found Ink (also by John Sundell!), Tuxedo, and Stencil.

The last three days have been a bit of a blur building Roland, so I may have my wires crossed. But the reason I didn't stick with any of those projects - even after getting proof of concepts working with all three - was they ranged from being a little too inflexible with how sub-templates were included to being really, really slow. I'm talking on the order of 1 second per page to generate. And I also realized that no matter how advanced or how much effort their authors put into the syntax and features of their template language, it would never be as flexible and powerful as...

PHP.

So, yes, Roland uses PHP to render your templates. I've long thought this and my work this weekend once again confirmed it for me, but PHP has always been the ultimate templating language for the web. I feel like every other template language I've ever used has always boiled down to a less-capable subset of PHP's features aimed at people who don't want to use a programming language. And that's totally cool. There's definitely use cases for that. But as a long-time PHP developer, I wanted the ease of use and power that a real language affords.

So, Roland (by way of Swift) runs your templates through the system PHP binary and outputs the results. It even allows for full use of include statements so you can structure your templates any way you want.

And while I realize how insane doing it this way may seem, Roland injects the data for your posts, pages, website metadata, and configuration settings into PHP's global scope, so that your templates can use all that information to render your website exactly as you want. Doing that live on a real web server would be nuts. But this is all pre-compiled and saved statically to disk ahead of time. When served to your visitors, there's no PHP being executed. So the security (and performance) implications aren't a thing.

Here's a sample template that renders the page for an individual post.

<?PHP include('inc/header.php'); ?>
<article>
  <header>
    <h1><?PHP echo $post_title; ?></h1>
    <p><a href="<?PHP echo $post_permalink; ?>"><?PHP echo dater($post_date, 'F j, Y'); ?></a></p>
  </header>
  <div>
    <?PHP echo render($post_content); ?>
  </div>
  <?PHP include('inc/post-categories-list.php'); ?>
  <nav>
    <div>
      <?PHP if(isset($post_previous_post_id)) : ?>
        <?PHP $p = $site_Posts[$post_previous_post_id]; ?>
        <a class="previous-post" href="<?PHP echo $p['permalink']; ?>"><?PHP echo $p['title']; ?></a>
      <?PHP endif; ?>
      <?PHP if(isset($post_next_post_id)) : ?>
        <?PHP $p = $site_Posts[$post_next_post_id]; ?>
        <a class="next-post" href="<?PHP echo $p['permalink']; ?>"><?PHP echo $p['title']; ?></a>
      <?PHP endif; ?>
    </div>
  </nav>
</article>
</main>
<?PHP include('inc/footer.php'); ?>

The post's properties like title, date, permalink, content, etc. are exposed natively to PHP like $post_title. You can easily create dynamic layouts by doing things like

<?PHP if(isset($post_previous_post_id)) : ?>

to decide if a link to the next post should be shown or not.

Having all of this data available to each page and exposed to PHP means I can finally make some interesting choices and optimizations that I've long been putting off because it would have otherwise meant mucking around with WordPress plugins. For example, all of the video content for this blog is hosted with BunnyCDN and played with VideoJS. Unfortunately, even minified, the JavaScript and stylesheets for rendering video are my largest static assets. But now, at build time, I only include those assets in the HTML on pages that specifically contain video elements. If a visitor browses this blog and never encounters a page with video on it (which is the vast majority), then that's an additional 350 KB they don't need to download. That's nothing on a modern connection. But on mobile? Every byte adds up.

Miscellany

Categories

I mentioned earlier that categories are placed in the post's front matter. That works fine, but it doesn't allow you to define a hierarchy of categories - just a flat list.

So, Roland will look for a categories.txt file in your project directory that allows you to define parent categories and subcategories.

The first category on each line is a parent. Any additional, comma delimited catogries that follow the parent on the same line will become children.

I'm not overly thrilled with the dumbness of how that file is structured, but, hey, it solves the problem for now.

Global Settings

And because I'm an Apple developer, the global configuration settings for your website are stored in a .plist file.

This contains settings such as

  • Base URL of the website
  • Title of the website
  • Posts per page
  • Whether or not to show post excerpts or the full post on every page
  • etc.

You can also define your own custom key pairs which are exposed to your templates in PHP. For example, I've added

  • My CDN's base URL which is prefixed in front of all static assets
  • Default post author
  • Title of the generated RSS feed
  • etc.

By default, when you run roland, it will look for config.plist to build your website.

But you can also include multiple property lists - one for testing locally, another with settings for a staging server, and one for production. Then, just tell roland which one to use at build time using the --config flag like

roland --config production.plist

Project Status

As I've said a few times now, Roland was built over a three-day weekend. It's pretty opinionated in the type of website it will currently output - namely, a blog.

I don't overly love the way the Swift side of things is structured. Don't get me wrong, it totally works! (For my purposes.) But there's certainly some tidying and cleaning up to do. I also want to make it a bit more general purpose so I can move clickontyler.com off Jekyll in the near future as well. Overall, I consider it a first draft.

I'd love any feedback folks can offer. Jekyll is the only real-life experience I've had with static website generators and has obviously influenced how I approached this project. On the other hand, maybe I'm just an idiot WordPress user and all of my pain points could have been avoided with one weird trick.

In any case, whether not it was worth the effort to build Roland from a technical point of view is certainly up for debate. But it sure was a fun distraction to hack away on. And it feels amazing to finally have complete control over my primary web presence.


Possibly Related Posts