UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION - UNDER CONSTRUCTION

Fronty

My custom static site generator

No, it's not an LLM generating my site. That's not what a static site generator is lol. Check my thoughts on LLMs and AI.

I'm not gonna lie. I love writing my own stuff. Even if it's shitty or bad. I, Like, Programming. Especially useful stuff.

And that's where Fronty comes in handy.

I have inherently nothing against other static site generators. I've used such generators myself. Hugo, for example. It's a fine generator, and has a vast array of templates and styles. It even has a very extensive templating language.

But it has too many bells and whistles for me. And I would either need to manually make a template, which would mean I have to learn the templating language, or go with a template and tamper with countless other files and folders, just so I can implement my own element on the site.

Instead of using someone else's static site generator, I could just make my own, all the while using my own templating language. How hard can it be?

[Insert whacky image of someone pointing back at another person, and asking "How do we tell him?"]

Let me show you a snippet of the template.


MainTitle() // First, we define a template name.
    <h1>My Cool Place<h1> // Second, we write the HTML that we want included
MainTitle // Third, we end the template

Then we just use the template like so:


<div class="main-page">
    MainTitle()
...

Easy peasy.

The implementation, however, isn't so easy peasy. The code right now is so ugly that I wouldn't want my worst enemy to work on it. I gotta refactor it at some point. But it is kinda fast. ~22µs at the fastest re-build, and 900µs at the slowest. Most of the time is spend reading the HTML files, so generation times are highly depending on storage speed and storage scheduling.

Features

Can't believe I forgot about features lol.

Supported file types:

  • .html
  • .css
  • .fnty

When running Fronty, it will create a map of all directories in the source directory of your site, and re-create it in a build folder. Doing re-build, Fronty will replace all template names with the associated HTML defined in any .fnty files.

Pretty simple. It's just search/replace. Now you might be asking, "Why not use RegEx?!?"

I don't like RegEx. Yep. I said it. I don't like RegEx. One must not forget this legendary StackOverflow comment.

Benchmarks

If you know me in real life, you know I like to spam benchmarks in our DMs. Almost to a religious degree. When making programming projects, I like to first make a "naive" implementation that just works and is intuitive. Then I bring in a benchmarking framework, in this case BenchmarkDotNet, and start working on different approaches to the naive implementation.

This is a small list of implementations that produces the same HTML after a build:

  • Builder.cs
  • SpanBuilder.cs
  • ReworkedPathBuilder.cs
  • DynamicFileBuilder.cs
  • FastFileWriterBuilder.cs
  • I did also have an AsyncBuilder.cs implementation, but it was so embarrassingly slow, that I decided to not go further with the idea.

TLDR: Async doesn't make sense for my use case.

When making anything resembling a web API or a server of sorts, and you have to load anything from a file system, or just any IO operation in general, it can be beneficial to mark that operation as async, as the program can then fetch the data and process other data at the same time.

It feels like multi threading without actually being multi threading. Or something like that. In reality, it kind of depends on what your program does. If it is a web server, then yes, making the loading async is for the best, as a method can hand over I/O to the operating system, return to caller, and resume at a later time, when the operating system has retrieved the data at a later time, all the while the thread can run other work while we wait.

Please be aware that async, at least in C#, does not imply multi threading. So if you have multiple async methods called from the same thread, they will not run at the same time. Although they might return on a different thread than they started on, they will not run in parallel, as they are async, not parallel. Parallelism means that methods are doing the work at the same time.

Async programming is not true multi threading, but it will feel like it. So when I made all of my methods async, and called the async overloaded methods for file-handling, what I really did was making my application wait for the data to load, while I could in theory do something else. While that is ideal, what really ended up happening was that I implicitly created additional state-machines and even more memory allocations. All my homies hate extra allocations.

See, the thing with async in C# is that it creates a state-machine, for when a method is allowed to run, or is to be paused. That needs additional memory to keep track of, and creates overhead of having to handle pausing and starting and checking if the method is running. That's wasteful for what I need, as everything is already running on the same thread, synchronously. I take data from a file, I process that data, I save that processed data. There is nothing else. I don't need interactivity, and I don't need to do multiple things in the same thread, as there is nothing else to do in the same thread. There is no other data to pull from at this moment in time.

Couldn't you just spawn a thread for each file to read?

Yeah, I could. But I still wouldn't need to use the async overloads, as we're now executing reading each file at the same time. So it wouldn't really matter all that much if they were async or not, as each work would still be blocked as they perform their own I/O work.

End of async rant.

Anyway, here are some benchmark results.

Method Mean StdDev Rank Gen0 Gen1 Allocated
FastFileWriter 273.4 µs 0.75 µs 1 34.1797 4.3945 282.01 KB
DynamicFileBuilder 294.1 µs 1.20 µs 2 44.4336 4.3945 366.24 KB
ReworkedPathBuilder 508.7 µs 11.53 µs 3 28.3203 0.9766 237.87 KB
SpanBuilder 563.0 µs 9.03 µs 4 36.1328 3.9063 296.55 KB
Builder 682.7 µs 34.35 µs 5 79.1016 7.8125 649.33 KB

Numbers explanation:

  • Mean: Arithmetic mean of all measurements
  • StdDev: Standard deviation of all measurements
  • Rank: Relative position of current benchmark mean among all benchmarks (Arabic style)
  • Gen0: GC Generation 0 collects per 1000 operations
  • Gen1: GC Generation 1 collects per 1000 operations
  • Allocated: Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)

Conclusion

I'm a bit sadden that the fastest method uses that much memory. I had hoped it would use less, but you can't win every time. I could potentially squeeze more speed out of it by implementing a custom reader. But I think this is fine enough. I could re-write it in a lower-level language, but I don't think I will lol. I've already spent enough time making it this fast, and honestly, I like how the code works and looks.

I can finally sleep again.

If you want, you can check out the code for Fronty on my git right here: https://git.foxingitup.com/foxingitup/Fronty

© MyCoolPlace. All Rights Reserved.