About This Site
TL;DR
This site is statically generated by Nix and Typst, with every page written in pure Typst syntax. The Nix pass compiles the page sources into a site graph defined as a fixpoint, in which each node holds a page and its metadata. The Typst pass then renders every node to HTML.
Self
The source of this page is literally the following:
#set document(
title: [About This Site],
author: "Gongqi Huang",
)
// Customize per-page style.
#show figure: it => {
if target() == "html" { html.frame(it) } else { it }
}
// Tired of writing it all out again? Let's borrow the site index
// page's style. Here, `sys.*` (or `sys(*)`) is secretly a Nix
// evaluator. Or, we could just import a .typ library instead :)
#sys.site.index.showRule
#title()
// Every page defines an overlay constructor, which returns this
// page's site overlay.
#let _overlayFn = ```nix
# PS: Turns out Typst does not currently support nested raw block
# syntax highlighting (https://github.com/typst/typst/issues/2844).
# Sad.
# Utilities provided by the site generator.
{
lib, # Nixpkgs lib
contentFn, # Content function of the current page:
# It generates the final .typ source for the
# Typst pass. And nothing stops you from calling
# other content functions!
... # ... and more
} @ctx:
# Site overlay. `site` is the site graph.
site: {
# `content` contains the final bytes for the Typst pass.
content = contentFn (ctx // { inherit site; });
# Metadata for the generator, or to share with other pages.
dateCreated = null;
dateModified = null;
# Now, let's generate the site map you saw at the top of
# the page. Here we transform the site graph into a
# JSON-friendly tree carrying only what the map needs.
# This could live anywhere in the page, but we put it here
# so other pages can reuse it.
siteTree = let
siteTree' = s:
let
nameOf = p:
if lib.last p.route == "index.html"
then
let n = lib.length p.route;
in if n == 1
then "/"
else lib.elemAt p.route (n - 2) + "/"
else lib.last p.route;
kids = removeAttrs s [ "index" ];
in {
route = if s ? index then s.index.routeStr else null;
name = if s ? index then nameOf s.index else null;
children = lib.mapAttrsToList
(_: v: if v ? content
then { route = v.routeStr;
name = nameOf v;
children = [ ]; }
else siteTree' v)
kids;
};
in siteTree' site;
}
```
// Let's pull a graph generator from Typst Universe, so we don't have
// to build our own graph library and speak HTML by hand!
#import "@preview/tdtr:0.6.1": *
// Typeset the site map with the tree we just built... and we're done!
// I'll see you at the beginning, friend :)
#let site = json(bytes(sys(```nix
quote (builtins.toJSON site.blog.about-self.siteTree)```)))
#let here-route = sys(```nix quote site.blog.about-self.routeStr```)
#let build-site-map(site-node, here-route) = {
import tidy-tree-builds: *
let is-here = site-node.route == here-route
let content = {
link(site-node.route, raw(site-node.name))
if is-here {
metadata("here")
node-attr(rotate: -30deg)
}
}
node[#content]
if is-here {
leaf[
#text(font: "Comic Neue")[You are here!]
#metadata("here-text")
]
}
for c in site-node.children { build-site-map(c, here-route) }
up()
}
#figure(
tidy-tree-graph(
{ build-site-map(site, here-route) },
text-size: 12pt,
node-inset: 0.5em,
spacing: (2.5em, 0.8em),
draw-node: (
tidy-tree-draws.horizontal-draw-node,
(corner-radius: 2pt, defocus: 0),
tidy-tree-draws.metadata-match-draw-node.with(
matches: (
here: (fill: none, stroke: 1pt),
here-text: (stroke: none, fill: none, inset: 0pt),
),
default: (stroke: 0.25pt),
),
),
draw-edge: (
tidy-tree-draws.horizontal-vertical-draw-edge,
(marks: "-"),
(from, to, _) => {
let direct = from.pos.x == to.pos.x
let v_from = (name: from.name, anchor: "east")
let v_to = (name: to.name, anchor: "west")
let v_mid = {
(rel: (12pt, 0pt), to: (name: from.name, anchor: "east"))
}
(
vertices: if direct {
(v_from, v_to)
} else {
(v_from, v_mid, ((), "|-", v_to), v_to)
},
corner-radius: 2pt,
)
},
tidy-tree-draws.metadata-match-draw-edge.with(
to-matches: (here-text: (from, to, _) => (
vertices: (
(rel: (-5pt, 0pt), to: (name: to.name, anchor: "west")),
(rel: (5pt, 0pt), to: (name: from.name, anchor: "east"))
),
marks: "->",
bend: 35deg,
stroke: 0.8pt,
)),
),
),
),
supplement: none,
)
// The beginning. But you don't need to read the rest of the
// source. Go enjoy the fully rendered text.
= TL;DRThe rest of the source.
This site is statically generated by Nix and Typst, with every page
written in pure Typst syntax. The Nix pass compiles the page sources
into a site graph defined as a fixpoint, in which each node holds a
page and its metadata. The Typst pass then renders every node to HTML.
= Self
The source of this page is literally the following:
#let foldable(summary, body) = {
html.elem("details", {
html.elem("summary", summary)
body
})
}
#let src = sys(`quote (lazySource {})`)
#let end = "= TL;DR"
#let idx = src.position(end) + end.len()
#raw(src.slice(0, idx), lang: "typst", block: true)
#foldable[The rest of the source.][
#raw(src.slice(idx), lang: "typst", block: true)
]
= Credits
The idea of using Nix to build a self-referential static site
generator comes from my labmate, #link(sys(```nix quote
data.people.lschuermann.website```))[Leon Schuermann]. This site also
leans on amazing third-party packages from Typst Universe (e.g.,
`tdtr` for the above site map), and on Typst's HTML support.
#foldable[
Just so you know, I can print this page's overlay constructor.
][ #_overlayFn ]
// The end is never the end is...
// Didn't I tell you to read the nicely rendered text?
Credits
The idea of using Nix to build a self-referential static site generator comes from my labmate, Leon Schuermann. This site also leans on amazing third-party packages from Typst Universe (e.g., tdtr for the above site map), and on Typst’s HTML support.
Just so you know, I can print this page’s overlay constructor.
# PS: Turns out Typst does not currently support nested raw block
# syntax highlighting (https://github.com/typst/typst/issues/2844).
# Sad.
# Utilities provided by the site generator.
{
lib, # Nixpkgs lib
contentFn, # Content function of the current page:
# It generates the final .typ source for the
# Typst pass. And nothing stops you from calling
# other content functions!
... # ... and more
} @ctx:
# Site overlay. `site` is the site graph.
site: {
# `content` contains the final bytes for the Typst pass.
content = contentFn (ctx // { inherit site; });
# Metadata for the generator, or to share with other pages.
dateCreated = null;
dateModified = null;
# Now, let's generate the site map you saw at the top of
# the page. Here we transform the site graph into a
# JSON-friendly tree carrying only what the map needs.
# This could live anywhere in the page, but we put it here
# so other pages can reuse it.
siteTree = let
siteTree' = s:
let
nameOf = p:
if lib.last p.route == "index.html"
then
let n = lib.length p.route;
in if n == 1
then "/"
else lib.elemAt p.route (n - 2) + "/"
else lib.last p.route;
kids = removeAttrs s [ "index" ];
in {
route = if s ? index then s.index.routeStr else null;
name = if s ? index then nameOf s.index else null;
children = lib.mapAttrsToList
(_: v: if v ? content
then { route = v.routeStr;
name = nameOf v;
children = [ ]; }
else siteTree' v)
kids;
};
in siteTree' site;
}