summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrefetch2022-11-15 22:13:52 +0100
committerPrefetch2022-11-15 22:13:52 +0100
commit4880f641ade11e60f18f907fdf0660ca349714a7 (patch)
tree4dad9d35432d4bd1f893d188cf6318da9362d901
parent5ed7553b723a9724f55e75261efe2666e75df725 (diff)
Publish "Website adventures" part 1 about static site generators
-rw-r--r--source/blog/2022/things-i-use/index.md11
-rw-r--r--source/blog/2022/website-adventures-generators/index.md474
2 files changed, 481 insertions, 4 deletions
diff --git a/source/blog/2022/things-i-use/index.md b/source/blog/2022/things-i-use/index.md
index 65247c5..db163eb 100644
--- a/source/blog/2022/things-i-use/index.md
+++ b/source/blog/2022/things-i-use/index.md
@@ -11,7 +11,7 @@ so I've made a list of the programs I like enough to recommend.
Such a list has been on my website for a long time already;
this is its official publication.
-Last updated on 2022-09-28.
+Last updated on 2022-11-11.
## General
@@ -103,12 +103,15 @@ Last updated on 2022-09-28.
It has lots of advanced features that I barely understand,
but still seems to be the most modern and usable spam filter out there.
* [Zola](https://www.getzola.org/):
- Straightforward static site generator written in Rust.
- The only thing it's missing is some kind of LaTeX formula support,
- which is why I migrated to Hugo.
+ Static site generator written in Rust.
+ It's fast, flexible and stays out of your way.
* [Hugo](https://gohugo.io/):
Another good static site generator, although not quite as nice as Zola in my opinion,
since Hugo's template language is a bit messed up. It still works well though.
+* [Jekyll](https://jekyllrb.com/):
+ Yet another static site generator, in Ruby this time.
+ It's very popular for good reason,
+ and has a wealth of plugins if you need extra features.
* [cgit](https://git.zx2c4.com/cgit/about/):
JavaScript-free online Git frontend,
perfect for private setups.
diff --git a/source/blog/2022/website-adventures-generators/index.md b/source/blog/2022/website-adventures-generators/index.md
new file mode 100644
index 0000000..0aba183
--- /dev/null
+++ b/source/blog/2022/website-adventures-generators/index.md
@@ -0,0 +1,474 @@
+---
+title: "Adventures in making this website:<br>static site generation"
+date: 2022-11-15
+layout: "blog"
+toc: true
+---
+
+Making and managing this personal website has been an adventure.
+In this series, I go over the technical challenges I've encountered
+and philosophical decisions I've made,
+and I review some of the tools I've used along the way.
+This is part 1, with more posts coming soon.
+
+
+
+If you've ever taken a good look at HTML,
+it should be clear that it isn't fun to write it all by hand.
+Therefore, we invented the Static Site Generator (SSG):
+a program that takes an HTML template and some text
+in a more human-friendly form (usually Markdown),
+and mashes them together, yielding a servable HTML file.
+Of course, it's pretty clear what the best approach to do this is,
+so we only need one SSG, right?
+
+<a href="https://xkcd.com/927">
+<img src="https://imgs.xkcd.com/comics/standards.png" style="border: 5px solid #ffffff;">
+</a>
+
+Yeah... no, there are [a lot of them](https://jamstack.org/generators/).
+Frustratingly many, in fact.
+Although some of them are highly specialized or advanced,
+most are very similar in scope.
+The three SSGs I'm about to discuss
+are all intended for personal blogs,
+and for many people, there isn't much reason to prefer one over the other.
+My SSG-hopping was due to my niche requirements and perfectionism.
+
+
+
+## Zola
+
+The first SSG I used was [Zola](https://www.getzola.org/),
+which was started in late 2016 (making it the youngest SSG here),
+and advertizes itself as a *"one-stop static site engine"*
+that *"gets out of your way so you can focus on your content"*.
+Written in Rust, it's fast and portable,
+and has convenient features like code syntax highlighting,
+support for multilingual sites, and an optional theme library.
+Similar to other SSGs, you can run `zola serve` to start a local HTTP server
+to test a webpage, which reloads itself each time you make changes;
+this makes development a breeze.
+
+
+
+### Demonstration
+
+An example can say more than a thousand words,
+so, to give an idea of what it's like to use Zola (other SSGs are similar),
+consider this HTML template,
+powered by [Tera](https://tera.netlify.app/):
+
+```html
+{% raw %}<html>
+ <head>
+ <title>Example</title>
+ </head>
+ <body>
+ {% block content %}
+ {{ section.content | safe }}
+ {% endblock content %}
+ </body>
+</html>{% endraw %}
+```
+
+This is a standard HTML document, except for the parts in curly braces:
+`{% raw %}{% %}{% endraw %}` is for statements,
+and `{% raw %}{{ }}{% endraw %}` inserts the value of an expression into the output.
+We've created a block `content`, and given it the default definition
+`{% raw %}{{ section.content | safe }}{% endraw %}`,
+where `section.content` is the content of this "section"
+after Markdown-to-HTML conversion
+(Zola distinguishes *sections* and *pages* based on folder structure and file name).
+Then `safe` tells Tera that the string has been fully converted to HTML,
+so no more escaping is necessary.
+
+The point of defining such blocks in the template
+is that you can override them.
+We thus create a page template which inherits the above structure
+(`{% raw %}{% extends "default.html" %}{% endraw %}`)
+and redefines the `content` block.
+This example also highlights Zola's neat table-of-contents generation:
+
+```html
+{% raw %}{% extends "default.html" %}
+{% block content %}
+ <div id="title">{{ page.title }}</div>
+ <p>
+ <ul>{% for h1 in page.toc %}
+ <li><a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
+ {% if h1.children %}
+ <ul>{% for h2 in h1.children %}
+ <li><a href="{{ h2.permalink | safe }}">{{ h2.title }}</a></li>
+ {% endfor %}</ul>
+ {% endif %}</li>
+ {% endfor %}</ul>
+ </p>
+ {{ page.content | safe }}
+{% endblock content %}{% endraw %}
+```
+
+Okay, enough templates, what if we actually want to write something?
+Well, then we just create a Markdown file in the appropriate place
+(I'm being vague, since this isn't intended to be a tutorial)
+with contents like:
+
+```markdown
++++
+title = "Example page"
++++
+
+# Heading 1
+
+Lorem ipsum dolor sit amet...
+```
+
+The part surrounded by `+++` is called the *front matter*,
+and is a common feature in SSGs, allowing you to set parameters
+that can be used by templates or the SSG itself.
+And that's it! If we now run `zola build`,
+our Markdown will be converted to HTML and inserted into the template.
+
+
+
+### Review
+
+Zola's opinionated design pays off:
+it's quite simple, fast and as flexible as your templates allow.
+Speaking of which, of the SSGs I've tried,
+Tera is in my opinion the cleanest and most powerful template engine.
+Zola's main weakness is its obscurity:
+if you need help or want to do complex things,
+there aren't many resources available other than the official documentation.
+But I also think its design is good enough
+that you can probably figure it out.
+
+So, why did I stop using Zola? Due to a giant misunderstanding, that's why.
+In late 2020 I started my [knowledge base](/know/),
+for which I needed the ability to show maths formulas.
+There exist several solutions to put LaTeX maths on websites,
+but back then I didn't understand how they worked and what their prerequisites were,
+so I mistakenly concluded that it wasn't possible with Zola.
+Indeed, Zola doesn't have built-in support for it,
+but it turns out I didn't need that!
+And I only figured that out while writing this post...
+
+I'll go over my maths rendering journey in a future article.
+In the end, my misguided decision to migrate from Zola did have some benefits;
+otherwise my current offline solution wouldn't have been possible.
+But in hindsight, I wish I'd spent more time with Zola,
+and I think it would be my first recommendation to a beginner.
+
+
+
+## Hugo
+
+I migrated to [Hugo](https://gohugo.io/), an SSG written in Go,
+which also markets itself as being fast and flexible,
+and is pretty much feature-equivalent to Zola.
+Hugo is much more popular than Zola;
+in fact, it seems to be one of the most-used SSGs out there.
+
+The reason I chose it is that it allows page contents to be written in one
+of [several formats](https://gohugo.io/content-management/formats/#list-of-content-formats),
+one of which is [pandoc's](https://pandoc.org/) Markdown dialect
+that has built-in support for LaTeX maths.
+So when Hugo is building the site, it passes my `.pdc` file to the external `pandoc` program,
+which converts it to HTML to insert into the template.
+With how Hugo sets this up, the result is that all maths
+get wrapped in HTML `<span>` tags with certain classes,
+which I (wrongly) thought was necessary.
+
+
+
+### Demonstration
+
+The previous page template example would look like this in Hugo,
+after a few small tweaks like adding a navigation bar at the top of the page:
+
+```html
+{% raw %}<html>
+ <head>
+ <title>{{ .Title }} | {{ .Site.Title }}</title>
+ </head>
+ <body>
+ {{ partial "navbar.html" . }}
+ <div id="title">{{ .Title }}</div>
+ {{ .TableOfContents }}
+ {{ .Content }}
+ </body>
+</html>{% endraw %}
+```
+
+On the one hand, Hugo provides the convenient `.TableOfContents` variable
+at the cost of control, unlike Zola, where we had to do it manually.
+On the other hand, Hugo doesn't use block-based templates
+and hence lacks a nice inheritance system like Zola's.
+If you want to reuse a snippet, you put it in a file e.g. `navbar.html`
+and include it as `{% raw %}{{ partial "navbar.html" . }}{% endraw %}`.
+Note the dot at the end... I'll get to that.
+Overall, Hugo's approach to organizing templates feels "dumber" than Zola's,
+although, to be fair, my website had become a lot more complicated by then.
+
+Like most SSGs, Hugo uses an external template engine,
+namely the [`text/template`](https://pkg.go.dev/text/template) Go package.
+Disclaimer: I'm not a Go developer, so I don't know how nice this library is for various use-cases;
+here I'm just reviewing whether it's good for HTML templates in Hugo.
+
+Okay, here comes my review: it's awful. No, it isn't a good fit here:
+the whole point of an SSG is to have a pretty sophisticated template language
+to avoid needing to program a custom solution.
+This package is just too primitive,
+so Hugo has shoehorned all sorts of extensions into it to make it usable;
+the result is an opaque mess with still too little flexibility.
+For simple stuff like inserting the site's title somewhere it's fine,
+but I need some pretty non-trivial templates.
+
+To give you a taste, consider the following snippet I wrote,
+which lists the 10 most recent articles from newest to oldest,
+as seen on my knowledge base' [front page](/know/):
+```html
+{% raw %}<p>Most recent articles:
+ <ol>
+ {{ $concepts := where .RegularPagesRecursive "File.Dir" ">" "know/concept/" }}
+ {{ range first 10 $concepts.ByPublishDate.Reverse }}
+ <li><a href="{{ .RelPermalink }}">{{ .Title | title }}</a></li>
+ {{ end }}
+ </ol>
+</p>{% endraw %}
+```
+
+This example captures the messiness of Hugo templates.
+But on a more serious note,
+there are some questionable design decisions here,
+in no particular order:
+
+* `.Title` is a variable, whose value is implicitly set by the surrounding `range` loop.
+ And what if you want to insert *this* page's title?
+ You also write `.Title`, just not inside a loop.
+ So you don't know what some variables contain,
+ unless you pay close attention to the context:
+ you need to be aware that this is happening inside a loop over an array of pages...
+ and not, of course, an array of something else,
+ so don't forget to type-check the loop.
+
+ To be fair, what's going on here is clearly explained
+ [in the docs](https://gohugo.io/templates/introduction/#the-dot):
+ the initial dot refers to the current context,
+ of which `Title` is an attribute.
+ The keywords `range` and `with` swap out the context from under your feet,
+ which I insist is bad design.
+ If you need the global context, it can always be accessed explicitly using `$.`,
+ so at least there's that?
+
+* Meanwhile, `.ByPublishDate` is a function that sorts a list of pages;
+ specifically, it's a *method* in the object-oriented sense.
+ Okay, this works, but why did they add this feature,
+ instead of just telling us to use the more general
+ [`sort`](https://gohugo.io/functions/sort/) function?
+
+ `.Reverse` is another method, which flips the order of an array.
+ I have the same question: why this, instead of a general `reverse` function?
+ As of writing, no such function exists,
+ so if you want to reverse an array... [have fun](https://discourse.gohugo.io/t/reverse-array/28753).
+
+ This awkward split between methods and "normal" functions
+ makes it unclear how things can be composed.
+ Clearly, you can chain methods as `$xs.ByPublishDate.Reverse`,
+ or pipeline functions as `$x | title | sha256`,
+ but how do you mix the two?
+ And what if we need to pass additional arguments,
+ e.g. for [`.Param`](https://gohugo.io/functions/param/)
+ or [`hmac`](https://gohugo.io/functions/hmac/) (WTF, this SSG can do crypto)?
+ I'm sure there exist answers to these questions;
+ my point is that it's unnecessarily confusing.
+
+* Nitpick: for looping over an array,
+ why did the Go developers choose the keyword `range`,
+ instead of `for` like almost all other computer languages?
+ To me, `range` is a function (like in Python),
+ and the order of `range first 10 xs` hence looks weird (coming from Haskell).
+
+ And why do [Hugo's docs](https://gohugo.io/functions/range/)
+ claim that `range` is indeed a function,
+ while the Go [package docs](https://pkg.go.dev/text/template#hdr-Actions)
+ clearly say that it's an *action*?
+ Documentation should be precise!
+
+Maybe, if you're a Go/Hugo veteran, this all just makes sense to you
+(then I'm both jealous and scared of you).
+I just want you to keep my comments in mind
+when I compare the same piece of templating code in the next SSG.
+
+
+
+### Review
+
+I don't mean to shame Hugo's developers with my complaints.
+I suspect it started as a private experiment
+(which would explain their choice of Go's template package for quick development)
+that became too popular too fast.
+Looking at [Jamstack's ranking](https://jamstack.org/generators/) of SSGs,
+we see that Hugo is the only compiled program anywhere near the top,
+and I think that explains its popularity:
+there are a lot of performance-focused programmers out there,
+who don't want an SSG in an interpreted language like JS or Python.
+So when Hugo was released, it quickly attracted many users,
+and also many contributors wanting various features.
+The result is its current messy feel.
+
+But for all my complaining, Hugo's popularity is a big advantage,
+with many tutorials available online in addition
+to the extensive official documentation.
+And, as it claims, Hugo is indeed fast and flexible.
+In practice, you only really interact with an SSG when writing your templates,
+and after that most of your time is spent on actual content,
+so although Hugo may sometimes get in your way during development,
+it might not be a problem for you.
+
+Towards the end of 2022, my maths rendering solution required
+fetching 450KB of compressed JS on page load,
+leading to a visible delay, which I wasn't happy with.
+I'd discovered it was possible to render maths offline,
+so no JS would need to be loaded,
+but this couldn't reasonably be done with Hugo,
+unless I forked it like [this guy](https://graemephi.github.io/posts/server-side-katex-with-hugo-part-2/)
+did... No thanks.
+It was time to change.
+
+
+
+## Jekyll
+
+Compared to Zola and Hugo,
+[Jekyll's](https://jekyllrb.com/) feature set is fairly basic:
+things like an automatic table of contents or multilingual pages
+need to be added using plugins.
+But on the upside it supports plugins at all,
+thanks to being written in Ruby, an interpreted programming language,
+unlike its compiled competitors.
+It's quite mature compared to other popular SSGs,
+and is what I'm using right now,
+since I was able to get statically-rendered maths thanks to a plugin.
+
+
+
+### Demonstration
+
+At first sight, Jekyll's [Liquid](https://shopify.github.io/liquid/) templates
+look very similar to Hugo's, due to its inclusion system:
+
+```html
+{% raw %}<html>
+ <head>
+ <title>{{ page.title }} | {{ site.title }}</title>
+ </head>
+ <body>
+ {% include navbar.html %}
+ {{ content }}
+ </body>
+</html>{% endraw %}
+```
+
+But where did our table of contents go? For that, we need
+the [`jekyll-toc`](https://github.com/toshimaru/jekyll-toc) extension
+and pipe all our content through the `toc` filter.
+Compared to Hugo, Jekyll has a trick up its sleeve here:
+you can nest templates inside the `{% raw %}{{ content }}{% endraw %}` object.
+So, let the above `default.html` be a skeleton for the actual page template,
+which we then define as:
+
+```html
+{% raw %}---
+layout: "default"
+---
+
+<div id="title">{{ page.title }}</div>
+
+{{ content | toc }}{% endraw %}
+```
+
+Another template could be nested in `{% raw %}{{ content }}{% endraw %}`, and so on,
+until the innermost block is filled with the converted Markdown,
+which, unlike for Zola and Hugo, can also contain template code.
+This approach is brilliant in my opinion: it's more flexible than Hugo
+and needs less boilerplate than Zola.
+Also, Jekyll doesn't try to guess which template should be used for what.
+
+A standard feature of Markdown is that you can write HTML directly,
+which gets copied verbatim while the rest is converted to HTML too.
+A minor annoyance is that [kramdown](https://kramdown.gettalong.org/),
+Jekyll's converter, insists on parsing the HTML I write,
+but isn't very good at it:
+for the image on [this page](/blog/2022/email-server-revisited/#motivation-1),
+why did it generate a closing `</source>` tag?
+That isn't valid! Fortunately, modern browsers are accustomed to this,
+so it gets interpreted correctly anyway.
+
+And what about my snippet to list the 10 most recent articles?
+In Jekyll it looks [like this](/code/prefetch-jekyll/tree/source/know/index.md):
+
+```html
+{% raw %}{% assign concepts = site.pages | where_exp: "item", "item.layout == 'concept'" %}
+{% assign newest = concepts | sort: "date" | reverse | slice: 0, 10 %}
+<p>Most recent articles:
+ <ol>
+ {% for item in newest %}
+ <li><a href="{{ item.url }}">{{ item.title }}</a></li>
+ {% endfor %}
+ </ol>
+</p>{% endraw %}
+```
+
+There, isn't that much better?
+Yes, it's a bit more verbose than Hugo,
+but at least it's pretty clear what's going on;
+with the documentation's help, you could write this yourself.
+Now it's bearable for me to write complex templates...
+although sometimes I maybe take it
+[a bit too far](/code/prefetch-jekyll/tree/source/_includes/image.html).
+
+
+
+### Review
+
+Jekyll has been consistently popular for many years, and I can see why.
+Its templating system is elegantly organized and flexible enough for most use-cases,
+it's infinitely extensible with plugins
+and plenty fast despite being written in Ruby.
+Although 5 years older than Hugo, it doesn't feel nearly as messy.
+
+Jekyll deserves some criticism for how it wants you to organize your files by default:
+in both Zola and Hugo, you put your markdown into a dedicated `content` folder,
+but in Jekyll you just dump it all directly into the root,
+meaning that configuration and content aren't clearly separated, which I don't like.
+Fortunately, you can fix this in `_config.yml` by setting `source`.
+
+I'm happy with my choice to move to Jekyll,
+and I don't want to do another migration ever again
+(~200 files would need to be converted).
+My site feels organized now,
+and it builds in seconds, even including the process
+of rendering all LaTeX maths into HTML.
+In fact, I'm so satisfied that I've even published my
+[source code](/code/prefetch-jekyll/tree/)!
+
+
+
+## Conclusion
+
+Making a functioning website isn't as hard as it seems,
+but making a *good* one does take time.
+Most of that time is spent writing HTML templates and especially CSS,
+and realistically, both will need to be tweaked again and again over time.
+But, in my humble opinion, it's worth the effort:
+it's a constructive activity with tangible results.
+I like seeing my work appear in a browser.
+
+Do you want to build your own website using an SSG?
+I recommend that you look at Zola first, with Jekyll a close second,
+if you can deal with its higher complexity.
+Personally, compared to those two, I wouldn't recommend Hugo,
+but for some people (probably Go developers)
+it just might be what they're looking for.
+