- part 1: Injecting environment variables into static websites using NGINX
- part 2: Customer-specific documentation with Jekyll
- part 3: Rich code documentation with Jekyll (this post)
Markdown is a rather simplistic markup language. As such, it is less powerful, but also easier to learn than e.g. Asciidoc or reStructuredText. We picked it because most developers and architects are already familiar with it.
Our documentation site contains diverse content, such as glossaries, tutorials and SDK manuals. Those can mostly be expressed using plain text. But architecture documentation especially relies heavily on diagrams and charts to illustrate concepts.
One option to include such content would be as images that are created with external tools. However, in many circumstances, we can also describe the figures as text, e.g. PlantUML or Graphviz.
Ideally, a developer would write the following code in a Markdown file:
PlantUML can render the above textual description into this SVG:
Jekyll doesn’t have built-in support of PlantUML, though, so we have to implement a plugin.
As I have described in a previous post, a plugin is a Ruby file that we can drop into the _plugins
folder.
For implementation, I took inspiration from the existing PlantUML plugin, which I adapted for our purposes.
The full code of our custom plugin is below (don’t worry, we’ll go through it in detail):
Let’s take a closer look.
A custom Liquid block is a class that extends, perhaps unsurprisingly, Liquid::Block
.
Whenever a pair of {% plantuml %}
and {% endplantuml %}
is encountered, Jekyll will call the render
method for us.
Ignoring the caching for now, the general flow of render
is:
- extract the PlantUML code (
content = super
); - write it to a temporary file (
Dir.mktempdir
,File.open
andf.write
); - run the
plantuml
binary (via theplantuml
helper method andsystem
) twice, once for SVG and once for PNG; - bundle the result in an
OpenStruct
for caching purposes; - encode the images as Base64 (
Base64.strict_encode64
); and finally - generate an HTML string that references the images not as files, but with
data
URIs.
The advantage of this approach is that we don’t have to generate auxiliary files, but obtain self-contained HTML.
For caching, we utilize Jekyll’s built-in caching library.
It provides us with a key-value store:
the key is the hash of the PlantUML code (hexdigest
), and the value is the struct from step 4.
When regenerating the site, Jekyll will not rebuild any diagrams that haven’t changed.
The plantuml
helper method also reads additional configuration from Jekyll’s _config.yml
, e.g. to select an appropriate PlantUML theme.
Finally, the plugin registers the custom block with register_tag
.
A similar approach works for Graphviz, where we use the dot
binary instead of plantuml
.
Conclusion
Jekyll plugins provide a lightweight way to include other asset types, such as diagrams, in statically-generated sites. This reduces page load times compared to client-side rendering, e.g. with Mermaid, and allows us to use a broad range of figure types.