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;DR
The 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;
}