Skip to content

Commit e458803

Browse files
committed
graphviz: optionally pre-generate the SVGs
The WebAssembly version of Graphviz is not exactly light (1.4 megabytes at the time of writing). And it wasteful to let everybody re-render the SVG client-side over and over again even though the diagram's definition hasn't changed. So let's add a script that performs that rendering "on the server side" (more precisely: during deployment). This script takes no arguments and post-processes the output of Hugo in `public/`. For performance reasons, it requires the list of files that contain Graphviz diagrams. With other sites, it might not matter much because there might only be a handful pages. However, on git-scm.com there _are_ thousands of files that do not contain any Graphviz diagrams, and therefore it is a necessary optimization to process only the files that _do_ contain Graphviz diagrams. To get that list, a new layout and page are added that Hugo processes, identifying said list of files and writing the result to `public/diagram-list.html`. Since this script runs via Node.JS and therefore lacks the convenient built-in HTML parser of browser-based JavaScript engines, a prerequisite is to run `npm install` so that the `node-html-parser` package is available. A note about the slightly inelegant way the `graphviz` class is added to the generated SVGs: Sadly, `node-html-parser` is not smart enough to handle `element.classList.add(...)` followed by `element.toString()` correctly, and as a result the output would _not_ have the added class. But there's always crude string processing to save the day. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent d9f9c5b commit e458803

File tree

5 files changed

+55
-1
lines changed

5 files changed

+55
-1
lines changed

content/diagram-list.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
outputs:
3+
- json
4+
---

hugo.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ markup:
1212
goldmark:
1313
renderer:
1414
unsafe: true
15+
mediaTypes:
16+
application/json:
17+
suffixes:
18+
- json
1519
module:
1620
mounts:
1721
- source: content

layouts/_default/single.json.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{{- $diagram_list := where .Site.Pages "RawContent" "like" "{{< graphviz" -}}
2+
{{- $diagram_list | jsonify -}}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"license": "MIT",
66
"devDependencies": {
77
"@playwright/test": "^1.47.2",
8-
"@types/node": "^22.5.4"
8+
"@types/node": "^22.5.4",
9+
"node-html-parser": "^7.0.1"
910
}
1011
}

script/graphviz-ssr.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env node
2+
3+
import { readFileSync, writeFileSync } from "node:fs"
4+
import { parse } from "node-html-parser"
5+
import Viz from "../static/js/viz-global.js"
6+
7+
/*
8+
* This script post-processes the site as generated via Hugo, replacing the
9+
* `<pre class="graphviz">` elements with inline `<svg>` ones, pre-rendered
10+
* via `viz-js`.
11+
*/
12+
;(async () => {
13+
for (const { Path: pathInPublic } of JSON.parse(readFileSync("public/diagram-list.json", "utf-8"))) {
14+
const path = `public${pathInPublic}.html`
15+
const contents = readFileSync(path, "utf-8")
16+
const html = parse(contents)
17+
const vizImport = html.querySelector('script[src$="viz-global.js"]')
18+
if (!vizImport) {
19+
console.error(`No 'viz-global.js' import found in ${path}; skipping`)
20+
continue
21+
}
22+
vizImport.nextElementSibling.remove() // remove the inline script
23+
vizImport.remove() // remove the import
24+
25+
for (const pre of html.querySelectorAll("pre.graphviz")) {
26+
const engine = pre.getAttribute("engine") || "dot"
27+
const svg = (await Viz.instance()).renderString(pre.textContent, {
28+
format: "svg",
29+
graphAttributes: {
30+
bgcolor: "transparent",
31+
},
32+
engine,
33+
})
34+
const dataURL = `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`
35+
pre.replaceWith(`<img src="${dataURL}" />`)
36+
}
37+
console.log(`Rewriting ${path}`)
38+
writeFileSync(`${path}`, html.toString())
39+
}
40+
})().catch((e) => {
41+
console.error(e)
42+
process.exitCode = 1
43+
})

0 commit comments

Comments
 (0)