Cr;Lf; powered by TwoFold

This website is now entirely powered by TwoFold (2✂︎f). And you shouldn't notice any difference. And that's a good thing.

I used to generate everything with Eleventy (11ty), but I started considering a switch back in early 2025 - I just didn't have all the pieces in TwoFold yet. With the release of v0.12, I now have most of the features I needed:

  • A mini template language inspired by Jinja2, Django, and Nunjucks, easing the transition from existing HTML templates. I implemented TemplateEngine inside TwoFold (in just 130 lines of TypeScript) to handle variable interpolation, if conditions, for loops, and more.
  • The duplicate tag - essential for generating dynamic tag pages.
  • A new Runtime evaluator that holds the full state of a TwoFold-parsed file. It can be passed around and evaluate files in a full or limited context, esentially allowing flexible, programmatic imports and renders.
  • A new AST for parsed tags. I initially thought I'd use it for the site, but ended up using the Runtime instead, less hacky. Still, building this tree was crucial, as I now use it for BFS tags.
  • BFS tags: by default, tags are evaluated depth-first, but some need to run before their children. Now both DFS and BFS are supported. BFS tags are rare. Examples: set, json, toml, freeze, protect.
  • childCtx: a new way for double tags to access the context of their inner children. I needed this for BLOG tags with TOML headers, for example.
  • Memory and disk caching as first-class libraries. We had a way cache tags for a long time (eg: cache the results of the weather API), but now they're available as reusable TwoFold modules.

There are four types of pages on this site, each handled differently by TwoFold:

  • Plain HTML pages (e.g., about, author): rendered from templates with a Markdown tag that reads .md files and injects them directly into HTML.
  • Mem pages: the first I implemented. Rendered directly from .md files. The only trick was I needed to track which pages were linked, so I could populate backlinks dynamically.
  • Blog and photo pages were straightforward. When importing from Markdown, I also cache them on disk - ready for use in listing pages.
  • Listing pages (Home, /notes, /articles, /photos, /tags, etc.) were trickier, until I added caching. Now, I avoid re-parsing all blog Markdown files on every tag.

The Photos pages were another complication. Previously, I used img-DB to generate a Markdown gallery. This time, I let TwoFold evaluate the HTML output from img-DB. Since TwoFold is an XML/HTML parser, I hooked the img tag to cache metadata into the blog cache.

I'll keep using img-DB to rename images and generate thumbnails. I also plan to build a special page to view all my images, track usage, and see where they're used. I already have the logic - just not a gallery yet.

Eventually, I hope to refactor as much as possible and move more functionality directly into TwoFold core tags.

As before, the code is fully open source: Github.com/croqaz/crlf/tree/twofold

Final thoughts

On a different note: I think I'm the only user of TwoFold and that makes me a tiny bit sad...

I started the project around July 2018 as a rewrite of an older project called Sticky, and I've worked on it almost every year since. It's a beautiful project. It wasn't just for me - I spent a ridiculous amount of time testing and documenting as much as possible.

I've shared it in a few places, but I'm not popular anywhere on social media, so I don't think anyone understands what TwoFold is. I don't even know how to describe it to someone with no context.

Anyway.

I guess the conclusion is that you shouldn't get discouraged, even if nobody is using your fancy app. Build it anyway.

@notes #crlf #twofold