actually really proud of myself but this post needs all the disclaimers
WARNING
Normal people: don’t do any of this. The whole post is me compensating for making Hugo do things it’s not good at.
Stick with [Markdown][markdown] if you use [Hugo][hugo]. Use shortcodes or render hooks if you want to make things interesting. Experiment with reStructuredText or Asciidoctor — but anything past a few pages slows builds dramatically. Move away from Hugo if you prefer those formats. Try [Nikola][nikola] for
rst
blogs. [Gatsby][gatsby] has a plugin to directly transformadoc
content. You have options!
Asciidoctor?
Asciidoctor is yet another lightweight formatting language, with official implementations in Ruby, JavaScript, and Java. Processing tools transform it into HTML, PDF, and other formats. Like Markdown, I find it easy to read and write the format. Like reStructuredText and [Org][org], it provides structures suited for technical and long form writing. Oh, and clearly labeled hooks for extending if the built-in structures don’t quite meet your needs.
What’s this got to do with Hugo?
Hugo shines with Markdown, but you can use other content formats as well. It supports Org files directly through go-org. reStructuredText is supported if you have rst2html.py
installed. Asciidoc and Asciidoctor are supported if you have their processors installed. And like [Jekyll][jekyll], Hugo supports HTML as an HTML authoring language if you tack some front matter onto it.
I enjoy the flexibility. And that bit about supporting HTML as an authoring language is about to come in real handy.
TIP
go-org is nice, but
ox-hugo
excels if you want Hugo support tightly integrated with Org mode.
So what’s the problem?
What’s up with this?
Sixteen seconds might look impressive compared to Jekyll. It’s more alarming if you know Hugo’s reputation for speed.
I think my Asciidoctor files might be causing this slowdown. I do have quite a few of them.
How to confirm this? Well, I could run hugo
in debug mode and scan the output.
Interesting. I only updated a single .adoc
file — this one — but Hugo rebuilds all of them. It also spends about 17 seconds doing so. 17,000 of the 17,235 milliseconds spent in this build go to rebuilding mostly unchanged Asciidoctor files.
Okay.
Fine I’ll do it myself
I could always build the adoc
files myself instead of making Hugo do it.
Hang on — is that even worth it?
How long does it take for a single process to build HTML from all the adoc
files in my site? Not much point in this idea if Asciidoctor takes 17 seconds on its own.
All right. Let’s try this with roughly the same arguments Hugo does with external helpers.
This fills a temporary folder with Asciidoctor’s generated HTML, keeping it out of Hugo’s way.
0.65 seconds to build all the .adoc
files.
So yes. Building them fresh myself is quicker than 17 seconds. That’s about what I figured, since Hugo apparently starts a fresh Ruby process for each adoc
file. I used a single process for all of them.
This experiment is worth pursuing further.
Give it a shot
It will be fiddly, though. I’m going to end up adding a build step, and complicating Hugo’s normally straightforward site generation process.
Keep the front matter
Asciidoctor has its own document header rules, but I don’t have to think too much about that. To better support static site generators, Asciidoctor can be told what to do with YAML front matter. I want front matter glued back to output before saving to Hugo’s content
folder.
You can extend Asciidoctor at multiple points in the conversion pipeline, with code blocks or full classes. I’ll register a block extension for the postprocessor stage: after the document has been converted, but before it gets saved.
What about page resources?
For adoc files, I’ll treat the Asciidoctor content folder as the source of truth. Cover images and other page bundle files go with the adoc
. build-adoc
will copy them over when converting files.
Only rebuild new stuff
I might save a little more time — and disk writes — by limiting my build to updated adoc and supplemental files.
Course, it helps to stop deleting BUILD_DIR
.
If processing a single file was more expensive, I’d use something more careful than a timestamp check.
Make it official
Let’s skip the gory details, but I eventually moved all the adoc
posts, notes, and drafts to their own folder. Now build-adoc
officially generates HTML content with YAML front matter for Hugo.
Since Asciidoctor finishes so promptly, I’ll run it every time I build the site.
What do we have now?
I finished my basic Asciidoctor + Hugo flow. How long does it take to build the site now? Let’s find out.
Every adoc
file gets processed in the first run.
Less than three seconds. I like that time more than 15-18 seconds.
I went to a bit of trouble to only process updated adoc
files. Does it help?
Less than two seconds. Then again, load from other system processes can add a second — or more, if I opened a browser tab to some JavaScript-intensive URL.
But it appears to help somewhat. And again, I get happy when there are fewer disk writes.
Highlighting code samples
So at first, Asciidoctor wasn’t highlighting code samples. I had :source-highlighter: rouge
in my document header, but it was being ignored. Rather than add preprocessor logic to ensure that the document header gets processed, I specified the same attributes for every file converted:
All good now, right?
Uh oh.
That’s not good.
When Hugo sees {{ … }}
in my new HTML content files, it thinks that’s a shortcode! That’s great if I want to invoke a shortcode. Not so great in a post with code samples for working with templates. Those aren’t supposed to get processed.
No problem. Rouge handles syntax highlighting for my adoc
files. I need to take tokens that have already been transformed and make sure paired double curly braces are replaced with appropriate HTML entities. All I need is a slight adjustment to Rouge::Formatters::HTML#safe_span
.
I’d prefer to subclass Rouge::Formatter::HTML
, but Asciidoctor chooses and creates formatters right in the middle of a highlight method. I would also need to create a new Asciidoctor adapter for syntax highlighting and update all my adoc
content to use that adapter. Great idea for later, but I don’t have that kind of time today.
I’ll monkey patch Rouge::Formatters::HTMLdirectly, redefining
safe_span` to perform the needed transformation.
[!NOTE] What about shortcodes I want to keep? This is just general advice to make Asciidoctor and Hugo play nice with each other. You don’t need to rebuild your entire site flow to use this information.
Say I’ve got a shortcode for displaying images as figures.
Asciidoctor transforms unsafe characters into HTML entities.
And it looks kind of embarrassing.
The solution? Wrap that shortcode in a passthrough macro.
Much better.
Now what do we have?
I’m not sure. Let’s find out with a typical build all
.
Yeah there’s a lot of stuff there I still need to write about. Long story short: by directly using Ruby to convert Asciidoctor files into HTML for Asciidoctor, build and test combined take noticeably less time than build alone when Hugo had to manage the whole thing. And it’s not that different from how ox-hugo
manages Org content. A similar approach would probably work for rst
files.
I like it for now. Keeps me from getting bored.
But — and this is a big but — I couldn’t recommend this approach for normal people with things to do. Site generation now has more moving parts, which I must test and maintain if I want to share the least little note_.
What now?
Yay, everything works!
What’s next? I’m not sure. Hugo is an ever-smaller piece of my site-building workflow. That’s somewhat intentional. Still grumbly about having to fiddle with all my Markdown files last year. But still.
- Probably explore some AsciiDoctor extensions. If most of the work happens when I write a file, I won’t care much if that file takes a second to turn into HTML. And there are so many to choose from, from Asciidoctor Diagram to the Extensions Lab and beyond.
- Maybe turn my shortcodes into macros? Write some of my own extension classes?
- Keep exploring site generators. I love to putter. A framework that encourages puttering might suit me better than Hugo. Eleventy, for example.
Backlinks
Got a comment? A question? More of a comment than a question?
Talk to me about this page on: mastodon
Added to vault 2024-01-15. Updated on 2024-02-02