Adding D2 to Hugo
I currently use a fork of a theme called poison for this site. It supports mermaid for generating certain visualisations (such as this one)
While mermaid is a fantastic tool, I prefer D2 because I think the syntax is a bit cleaner for certain diagrams and the output by default looks a bit nicer. It also supports more advanced diagrams like SQL tables.
Shortcode
Hugo supports something called shortcodes, which are essentially commands that can be replaced at build time with custom output.
{{<d2>}}
users {
shape: sql_table
id: int { constraint: primary_key }
email: string
name: string
}
servers {
shape: sql_table
id: int { constraint: primary_key }
name: string
created_at: timestamp
}
user_server_junction {
shape: sql_table
id: int
user_id: int
server_id: int
joined_at: timestamp
}
user_server_junction.user_id -> users.id
user_server_junction.server_id -> servers.id
{{</d2>}}
The source for the sql diagram generated before is wrapped in the shortcode.
At build time if the page contains d2 then a script is inserted that will generate the appropriate svg for each element on the page.
Note that d2 is a custom shortcode, one that can be found in the layouts/shortcodes directory for the theme.
d2.html
Although the source could be improved vastly, I’ve provided it here incase anyone would like to recreate it for themselves.
{{ if ne (.Page.Scratch.Get "hasD2") true}}
<script type="module">
import { D2 } from 'https://esm.sh/@terrastruct/d2';
const d2 = new D2();
const d2Elements = document.querySelectorAll('.d2');
for (const element of d2Elements) {
const useElk = element.textContent.toLowerCase().includes('sql_table');
const result = await d2.compile(element.textContent, { layout: useElk ? 'elk' : 'dagre' });
const svg = await d2.render(result.diagram, { ...result.renderOptions, noXMLTag: true, pad: 25, scale: useElk ? null : 1 });
element.innerHTML = svg;
}
</script>
{{ .Page.Scratch.Set "hasD2" true}}
{{ end }}
<div class="d2">
{{- .Inner | safeHTML }}
</div>
Future Improvement - Hugo Pipes
One improvement I would like to make in the future is to generate the diagrams once on the server rather then have every client do it. In order to do this I will need to use Hugo Pipes, which is a series of asset processing functions along with a native copy of d2 (the client uses d2.js).