Skill Issue logo

Skill Issue

Subscribe
Archives
November 14, 2025

Wytchcraft

This week and into next I'm on a little getaway. It's not a full break from work, though I am working a bit less this week. I'm off in coastal cabin with Amy and Roxie. We're staying a short distance from some of the best surf spots on the South Island. I'm taking full advantage of being a few minutes from the surf, rather than 1.5 hours.

B9AE3FE1-7F1F-43DF-B75F-E839171CC9C3.jpg

The season of the Wytch continues, despite having more client work on my plate right now. The goal is to move both the Super Good website and my personal site to it eventually. Is it wise to use a bespoke static site generator in production? Absolutely.

Surely you're not going to tell me you don't know at least one person who builds their personal site with Makefiles or some more esoteric system. I happen to know John's website was Makefile-driven until he decided he could make the builds near instant by using Rake. (Using Rake saved on the startup cost of repeatedly shelling out to Ruby.)

My personal site has been driven by a ton of things over the years. I've used Jekyll, Gatsby, Rails, Rack, Next.js, Middleman, Nanoc, and probably more that I can't think of right now. Oh yeah, a Makefile. I've been there too.

More Code-Reloading

One thing I've been working on getting just right is Wytch's code loading. I'm still not sure the abstractions are exactly where I want them, but what I've got is reasonable.

Separate listeners track the changes to both the site code (loading by Zeitwerk) and the content code (which we load ourselves).

module Wytch
  class ReloadCoordinator
    attr_reader :reload_lock

    def initialize
      @reload_lock = Concurrent::ReadWriteLock.new

      @site_code_dirty = true
      @content_dirty = true

      @start_site_code_listener = Once.new do
        Listen.to(Wytch.site.site_code_path) do
          @site_code_dirty = true
        end.start
      end

      @start_content_listener = Once.new do
        Listen.to(Wytch.site.content_dir) do
          @content_dirty = true
        end.start
      end
    end

    def reload!
      @start_site_code_listener&.call
      @start_content_listener&.call

      return unless @site_code_dirty || @content_dirty

      reload_lock.with_write_lock do
        if @site_code_dirty
          # Site code changed: reload site code then reload content
          @site_code_dirty = false
          Wytch.site.site_code_loader.reload
          Wytch.site.load_content
        elsif @content_dirty
          # Only content changed: just reload content
          @content_dirty = false
          Wytch.site.load_content
        end
      end
    end
  end
end

That class handles all the heavy lifting. A simple middleware consumes that object, telling it to reload on each request (which only does something if the code has changed).

module Wytch
  class SiteCodeLoaderMiddleware
    def initialize(app, coordinator)
      @app = app
      @coordinator = coordinator
    end

    def call(env)
      @coordinator.reload!

      @coordinator.reload_lock.with_read_lock {
        @app.call(env)
      }
    end
  end
end

I want to pull more abstractions out of the ReloadCoordinator. The repetition in the Once blocks is screaming to be pulled out. Maybe the coordinator doesn't even need to know about Listen at all. We'll see.

Vite Integration

Besides supporting static files (put them in public/ and they will be served by the development server and copied into the build folder), I also threw together a basic Vite integration that comes ready to go with new sites. It comes with one semi-controversial choice and one very controversial choice.

The semi-controversial choice: it comes preconfigured with TailwindCSS. I neither love nor hate Tailwind. I chose it for this because it makes it very easy to just throw some shit together. It takes two seconds to pull it out if you don't like it.

The very controversial choice: it comes preconfigured with ReScript. I'll be talking about ReScript on an upcoming episode of Dead Code, but if you're not already familiar with it, here's what you need to know: It's the thing we should all be writing instead of TypeScript.

I think I've written a little about it in previous Skill Issues, but ReScript is basically OCaml with a better Syntax that compiles to JavaScript (and has an extremely good interop story). I don't burden clients with this technology because it's not used widely, but it rules and I use it on all my personal stuff.

Next Features

This weekend I'll probably add a few more features and cut a release. Here's the short list:

  • There needs to be a better API for introspecting on pages.
  • New sites should have a site map (part of the motivation for page introspection).
  • I want new sites to come with a basic blog post implementation (with RSS feed) to serve as a reference for building out more elaborate content.

Ultimately, I think this thing is just shy of being ready to actually new up the projects for the Super Good and Jardo sites and get working on them. I'm going to hold off on a v1.0 release of Wytch until I ship the sites, though. I don't want to have to worry about breaking changes.

Master Boot Record

I've been on an MBR kick lately. Master Boot Record is an Italian music project that aims to create symphonic metal using only synthesizers. It's a ton of fun and the live shows are wild. They add live instrumentation to the synth tracks, creating something totally unique. I was fortunate enough to catch them when they came through Vancouver.

Here they are adapting e1m1 from the original Doom soundtrack.

Don't miss what's next. Subscribe to Skill Issue:
Bluesky GitHub X LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.