Skip to content

Commit 9a5e2a2

Browse files
committed
Optionally pre-render the Graphviz diagrams
Since the WebAssembly version of Graphviz is not exactly light (1.4MB at the time of writing), and since it is a waste to let everybody re-render the SVG client-side over and over again even though the diagram's definition hasn't changed, 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 this here site, it might not matter much because there are only a handful images here. However, I want to reuse the same method on git-scm.com where 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, identifyng 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 now to run `npm install` so that the `node-html-parser` package is available. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 37e69c9 commit 9a5e2a2

File tree

6 files changed

+203
-0
lines changed

6 files changed

+203
-0
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
@@ -9,6 +9,10 @@ markup:
99
goldmark:
1010
renderer:
1111
unsafe: true
12+
mediaTypes:
13+
application/json:
14+
suffixes:
15+
- json
1216
module:
1317
mounts:
1418
- 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-lock.json

Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"node-html-parser": "^7.0.1"
4+
}
5+
}

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)