building a new website (again)
meow. so this website is new. again. visually it's similar to the last one. but this one has an actual backend!! instead of it being 11ty, it's powered by lune! lune has very little for libraries on top of luau, and luau has very little to begin with, and there's very few routing libraries and even fewer templating libraries. so, that kinda left me to build my own. fun!
libraries
so for this i had to build a lot of my own stuff. i have plans of eventually open sourcing a lot of this, but at the moment it's too tired into kitty and is kinda a mess. i'm also very busy with school so i don't know when exactly i'll get around to doing that.
url routing
this one is pretty fun. i have a little router that's about 200 lines long and has support for adding middleware and such. the routes supported can't be anything super complex. right now it supports stuff like /activity/:id/
and exposes the id in the callback function, along with using :variable:
for matching everything afterwards. this is only used for including subrouters though. this does everything i need it to though. and if i ever need more from it, well, i wrote it! i'm familiar with it and can change it as needed.
templating!
this is even more fun, but has been a bit of a headache. i took a lot of inspiration from etlua and nunjucks, specifically how a function is made with lots of string concatenation. mine is a lot smaller though compared to either of those, especially nunjucks. my little compiler script is less than 200 lines long and does most of the work (everything else that does compiling is mostly just build time stuff to save the outputted files). at build time this generates luau bytecode that gets saved in .precompiled/ in .luauc files (along with .luau files for debugging). then when the server starts it loads all of these in! these template functions get some extra helpers passed in to interface with everything else as needed, and using {% %}
lets me run luau right in them. here's an example of that being used for the file that generates my sitemap.xml:
sitemap.xml
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> {% for _, item in ctx.items do %} <url> <loc>{{ item.absoluteUrl|safe }}</loc> </url> {% end %} </urlset>
sitemap.luau
local serde = require("@lune/serde") local DateTime = require("@lune/datetime") return function(templateInterface) return function(context) local ctx = context local templateContents = {} local blocks = context.blocks or {} context.blocks = blocks local _flash = templateInterface.readFlashMessages(context.lifecycleContext) local _setLayout local function layout(layoutName: string) _setLayout = layoutName end local function echo(...) for _, echoPart in { ... } do table.insert(templateContents, echoPart) end end local function showBlock(block) block = block or {} local output = table.concat(block, "") echo(output) end local function include(includedTemplateName: string, extraContext) local rendered = templateInterface.includeTemplate(includedTemplateName, context, extraContext) echo(rendered) end local function escape(str: string) return templateInterface.escapeString(str) end local function url(search: string, args: { [number | string]: string }?) return templateInterface.getUrl(search, args) end local function static(path: string) return templateInterface.getStaticPath(path) end local function json(obj: any, pretty: boolean?) return serde.encode("json", obj, pretty) end local function renderCsrfField() return echo(templateInterface.renderCsrfField(ctx._csrfValue)) end local function getFlashMessages() templateInterface.clearFlashMessages(context.lifecycleContext) return _flash end table.insert( templateContents, [===[<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ]===] ) for _, item in ctx.items do table.insert( templateContents, [===[ <url> <loc>]===] ) table.insert(templateContents, item.absoluteUrl) table.insert( templateContents, [===[</loc> </url> ]===] ) end table.insert( templateContents, [===[ </urlset>]===] ) if _setLayout then blocks.ROOT = templateContents return templateInterface.renderLayout(_setLayout, context) end return table.concat(templateContents, "") end end
(every file gets a big block at the top with utility functions)
this has worked pretty well for me so far, other than errors in my templates are hard to find without proper syntax highlighting and having to inspect the outputted luau to find errors. writing templates does feel very fun though in a weird way :3
others
as far as writing pretty much all this from scratch, i have kinda cheated a bit. i don't know how to write an html parser, so i just used the only one i could find on github that works in luau, and it's very weird to use. all the types that luau tries to find in it are incorrect making it even more difficult to use. i'm also using some open source markdown library, but i'd like to write my own. i do pull in base64 stuff and sha1 and sha256 from some very good libraries, but i think that's fine to not write myself.
indieweb stuff
a big motivator for wanting to redo my website was so i could do more indieweb things with it. i still receive webmentions through webmention.io, but now posts send webmentions out when posted. by default i syndicate everything to bridgy fed, and optionally, indienews, so that's kinda cool i think. i haven't gotten anywhere with micropub, microsub, or indieauth though. i have been very slowly working on indieauth but the spec is massive and i have no idea what i'm doing to be honest. also just, really busy with school and stuff.
database!!
at first i was planning to just wait for lune to support native modules and then sqlite, but i got very impatient. kinda was a good thing though, because i found couchdb which is the only database i've really seen i think that has a rest api. and it's just a rest api. which is great when you use a runtime where you don't have tcp sockets or whatever. so far using it has been pretty fun!! am a big fan of it tbh.
hosting
i'm running this website on fly.io. i've used fly.io in the past for splashcat and loved it a lot. getting kitty to run on fly.io was pretty easy. hardest part was getting lune to run in docker, which was pretty easy.
running couchdb on fly.io was not as easy though. annoyingly, fly.io doesn't have ipv4 for internal communication between machines, only ipv6. and couchdb doesn't support ipv6 very well (yet). i did figure it out though after a while with the help of some couchdb maintainers in a github issue, but it involved building couchdb from source and stuff. maybe i'll write a post about this someday? idk.
for both couchdb and kitty, each have 3 machines running. couchdb has all 3 constantly running because i'm not sure how replication would handle syncing if a machine was down for a long time. kitty's machines will stop if there's no traffic though, so that's kinda cool, although i do nothing to support it.
anyways, don't think i have anything else to say really. i'll try and write some more posts in the future for when i get micropub, microsub, and indieauth going. i'd also like to get some other stuff added like sleep tracking activities. i think that'd be cool. i need to also make the activity cards be a bit prettier. the old website had them show the webmentions and stuff, but don't have that here yet.
if you read all the way to this point, well, thanks for reading i guess!! if you do want to support me for some reason (why????) i guess i have github sponsors. but really ty for reading this far, considering this entire post is just like, a bunch of boring words written by a half asleep catgirl. i should get some sleep.