An image of a 3D rendered woman's face.
Duston

← Back to posts

I originally wrote this website in raw HTML/JS/CSS, then I rewrote it in a JS framework, then I rewrote it again in another JS framework, and now I've rewritten it again by writing my own static site generator.

You Can Just Do Things

A while back, before I had actually made this website someone suggested I use something like Hugo. I thought it wouldn't fit my needs. I was wrong, it would have been more than adequate. This site can certainly be generated with a fairly simple process. I've learned this over the past few days by simply doing it myself.

I recently saw this video by gingerBill:

I watched it and thought to myself: "Huh, that's neat, not that I'll ever do that." But a bit he says at the end "You can just do things." That stuck with me.

The phrase can come off as hollow at first glance. It did for me, I projected my own negativity into it. Upon reflection, I was wrong to think of it like that. I know from my own experience that in reality, "YCJDT" is true, useful advice.

I've looked at the decompiled source code of a lot of C# games (C# games being trivial to decompile). Sometimes they are written by people who had no idea what they were doing. I am NOT saying that from some elitist position, instead, I am speaking rather literally. Some of these games I've seen with terrible code include one of the best selling steam games of all time: Terraria.

In the specific version of Terraria I was digging into (some old version from around 2015/2016 or so, before any cleanups or refactors being the point) there was, I believe, literally no usage of any collection type other than plain arrays. If anything needed to be relational it probably just used managing the same indices instead of just using a key-value based collection.

Whoever was writing the code might simply have not known about things like dictionaries, hashmaps, queues, etc. Maybe they never needed to learn them. Sure, it would have made the process much easier in many places, but it didn't ruin the game. An unfortunate reality is that code quality of a game has almost no relation to the "fun-ness" or success of said game, but, that's another topic for another post. Good code doesn't make a good game. A good game makes a good game.

What I've noticed is that, when someone has a goal in their heart, they will go through absolute hell to achieve it. The point is, they "Just Did The Thing". It didn't matter their skill level, they made one of the best selling games of all time because they believed in their goal of making a game, and they passionately wanted it. At the time of writing, it ranks #7 in the list of top selling games. Even when someone doesn't actually know how to code, they've persevered through it and managed to cobble together the best they can do at realizing whatever is in their head into a real thing. Imagine what you can do when you actually do know how to code.

It's Simple

I'm positive that Bill did not mean YCJDT in any dismissive way whatsoever. I think he did mean to encourage the viewer based on the results of his experience writing his generator. It ended up encouraging me, and so I went back to the video and watched a little closer this time. It's really not a complicated process. Once I realized what was going on, it made sense to me. Writing your own generator is doing basically the same work as frameworks, it's all just HTML/CSS/JS at the end of the day, all the way down.

Your generator is injecting HTML inside other HTML in the right places. In the same way that compiled code is (usually) just machine code which is just binary which is (hopefully) just electrons on some metal, the web is the same but with HTML/CSS/JS. I guess WASM changes that a bit now?

All you have to do is write strings to a file. That's it. You just write strings as HTML to a file with the .html extension and ship it. What really made it click is when I was looking at the JDX or whatever weird code thing the framework was using to embed javascript code inside the HTML. In this case, it was the code responsible for "foreaching" over all the blog .md files and putting them into the website. I realized at that moment, you can do the same thing with code, just go over the .md files and put them into a folder that sits next to your index.html.

Of course there are a couple slightly tricky things you have to work out, but in my experience the entire process was fun. It was something I'd never done before and didn't think I'd ever do. I thought it was harder than it is in reality. That's a huge part of YCJDT for me. I tend to catastrophize things before they even happen, so it makes sense. I just assume something is this difficult scary black cloud and that I can't do it. That doesn't always have to be the case. You should at least try. You can just do things.

The Generator

The specifics of my implementation are not that interesting. Of course I wrote it in Odin🍺, which has firmly cemented itself as my favorite compiled language to use and appears in these blogs quite often. One thing I did differently from Bill is how I handled my Spotify and YouTube embeds. They way I've been doing them since I switched to Svelte is something like this:

Inside some markdown file:

  # Title for some markdown
  
  Text in markdown, blah blah spotify:
  <Spotify track="5lhIY0YgH7JhszpQNUC1dm?si=ef6e13ed0ff44ddf"/>

For YouTube, I was just manually making <video> blocks since I'd only linked a YouTube video once so far. I figured this wouldn't work with my generator, that I'd have to do something annoyingly tricky with multiple passes. Luckily, cmark has a great API. Basically, what I do is create a node iterator after parsing the markdown file and run through the nodes. If the node is an HTML_BLOCK type, I process it something this:

root := cm.parse_document_from_string(blog.content, cm.DEFAULT_OPTIONS)
// Pre render, handle custom <Spotify/> and <Youtube/> nodes.
pre_render_pass(root)
// Render to HTML string.
rendered := cm.render_html(root, cm.DEFAULT_OPTIONS)
...
// inside pre_render_pass proc: 

// Lots of code omitted or changed for brevity.
if strings.starts_with(line, "<Spotify") {
	track_id, sub_ok := strings.substring(
		line,
		strings.index(cloned, `track="`) + len(`track="`),
		strings.index(cloned, `"/>`),

	formatted_str := fmt.tprintfln(SPOTIFY_EMBED, track_id)
	
	new_node := cm.node_new(.HTML_Block)
	new_node.data = transmute([^]u8)strings.clone_to_cstring(formatted_str)
	new_node.len = cast(cm.bufsize_t)len(formatted_str)
	cm.node_replace(node, new_node)
}
...

cmark's node_replace function is super handy for exactly this. I just substring out info like the track id and format it into the SPOTIFY_EMBED string. Parse the node and then replace it with our custom HTML "component" instead.

// html_items.odin

// {0} = track ID
SPOTIFY_EMBED :: `<iframe src="https://open.spotify.com/embed/track/{0}?utm_source=generator" width="100%%" height="152" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>`

Note the {0} for where we plop in the track id. If this were still Svelte, every component would be it's own .svelte file in a components folder in the lib folder and it has to be this way because of xyz. Nah, here it can just be a constant string. It's doing the same thing at the end of the day.

Conclusion

This was so much fun to work on. Everything is so much simpler. The folder structure is so plain and clean now. I have all the control for how things are done. If I want even more control I could not use the commonmark library and do my own HTML rendering. I don't need to, but the options are there for everything. The one downside is that my code blocks weren't working like they were before. I'm unsure if it is a fault of cmark, but code blocks were not being generated with fine detail anymore. Before, with whichever Svelte plugin I was using, it would generate spans and tokens properly which lead to more accurate syntax highlighting.

The one concession I've had to make is importing highlight.js (which seems like a great library to be fair), make an index.js file, and call the 1 function to highlight all the code blocks properly. Not bad. This is an example of something that I have the option of ripping out and doing as a "compilation step" of the website. I wanted to avoid using any javascript, but it's fine for now. The next thing I want to add for sure is the ability to click on headers and use them as jump-to links using the id of the element. Doesn't feel like a real website until it has that for some reason.


← Back to posts