Blogging with Asciidoctor and Hugo

on

Finally, I’ve found a great blogging workflow with Hugo and Asciidoctor.

With the extensive built-in feature set of Hugo and the feature-rich text formatting options that Asciidoctor offers, it creates a blogging experience composed of easy content management and fun writing experience.

As of this writing, there’s only a handful of articles and discussions about blogging with Hugo and Asciidoctor so I’ll add my 2 cents into the pile with the recent version of Hugo and Asciidoctor. Think of it like an update report.

For future references, here are the following tools and their versions used for this post:

  • Asciidoctor v2.0.10

  • Hugo v0.57.2

Note
This is not a full-on tutorial, more like a tour guide of my blogging setup with Hugo and Asciidoctor.

Prerequisites

If you want to follow through the whole post, I assume you already satisfied the following conditions:

  • Installed Hugo and Asciidoctor

  • Already know the basics of both tools

  • Already has a Hugo project with a theme installed

  • OPTIONAL: A Travis CI account (or similar CI/CD services)

  • OPTIONAL: A GitHub account (or similar remote Git repo)

Tip
If you’re not familiar with Hugo and Asciidoctor, they both have a quick start guide. Here’s for Hugo and Asciidoctor.

Asciidoctor and Hugo

In most static site generators including Hugo, Markdown is the one and only first-class citizen when it comes to creating posts. However, in recent Hugo versions, there exists the external helpers feature which calls appropriate external programs to certain type of files (or file extension). Fortunately, Asciidoctor-based files are automatically compiled with Asciidoctor so we don’t need to do anything. Just have it installed and you’re raring to go.

Creating content with Asciidoctor

Creating Asciidoctor-based content in a Hugo site is very easy. Just create an Asciidoctor file manually or you could go with Hugo’s way which is the optimal way.

hugo new posts/my-first-post.adoc

And there should be a new Asciidoctor file at content/posts/my-first-post.adoc.

Most likely, you would see that it’s formatted like a Markdown file since most themes do not have focus for Asciidoctor.

One of the features of Hugo is letting you create content templates (or an archetype) for your usual content. We create content with Asciidoctor so let’s create a quick template for that.

Create a file in archetypes/default.adoc. This will be the master template whenever Hugo detects the new content has a file extension of .adoc. Then, create a template for your Asciidoctor documents.

To get an example, here’s my template for my Asciidoctor documents.

--- (1)
title: "{{ replace .Name "-" " " | title }}" (2)
date: {{ .Date }} //
draft: true

categories:
    - "category1"
tags:
    - "tag1"
    - "tag2"
---

= {{ replace .Name "-" " " | title }}
{{ .Site.Author.name }} {{ with .Site.Author.email }}<{{ . }}>{{ end }} (3)
{{ dateFormat "2006-01-02" .Date }} (4)
  1. The frontmatter. Unfortunately, we would still have to put this for Hugo to recognize this document as one of the content.

  2. Converts the slug of the document to title case.

  3. Putting the author in the Asciidoctor preamble along with the email (if there’s any). Feel free to discard it.

  4. The date in ISO format.

Note
Speaking of frontmatters, native Asciidoctor frontmatter is not recognized.

You can modify the template to your heart’s content. For example, if you use MathJax for writing mathematical formulas, you can add the stem attribute (:stem:).

Since Asciidoctor-based documents only recieve basic support, you still need to do some work yourself before you get satisfied with the settings. For example, enabling syntax highlighting and styling certain things like callouts, admonition blocks, and open blocks.

Also, not everything is 100% working so you might encounter some problems which is discussed later in the post. Nonetheless, it works for the most part and you can still write expressively with the heavier feature set of Asciidoctor.

Syntax highlighting (without the shortcode)

Note
As of 2019-09-25, I don’t use a syntax highlighter anymore for my site in the name of performance and consideration for low internet speeds. Clearly, I didn’t think ahead about this.

Syntax highlighting can be an important feature for technical blogs especially if you often have to show code in your posts.

On Asciidoctor, you can enable syntax highlighting with the :source-highlighter: attribute. You can compile it on runtime with the executable but it’s not possible with Hugo since the arguments passed to it is hardcoded. You can, however, enable it for every document you have but as you might imagine, it’s not ideal and requires some manual labor.

If you’re only relying on the out-of-the-box features from Hugo (READ: if), you can get it with the highlight shortcode which is going to bite back if you’re going to migrate to another blogging platform or static site generator.

Still, there are some ways with getting syntax highlighting for your Hugo site without the Asciidoctor attribute or the Hugo shortcode. It’ll just take some more effort to get through.

One of the more reliable ways on enabling it is using syntax highlighters like highlight.js or PrismJS. I’ll be discussing on setting it with PrismJS since it easier and that’s what I’m mainly using on my blog.

For future references, the version of PrismJS I’m using is at v1.17.1.

Getting the files

First, we are going to need the syntax highlighter scripts along with their stylesheets, of course. I recommend to save the files locally instead of linking them through a CDN since they’re often prebuilt with limited languages and settings support.

Getting the files for PrismJS is very easy.

  • Go to the download page.

  • Select the minified version.

  • Select all of the languages you think you need to support.

  • Include the "Keep Markup" plugin.

  • Download it.

You’ll need the "Keep Markup" plugin in case you use Asciidoctor callouts since PrismJS replaces the HTML elements along with their classes.

With the script downloaded, place them somewhere in your Hugo project. For this purpose, I’ll assume the script is in the static/js/lib/SYNTAXHIGHLIGHT.js.

Don’t forget to choose a theme as well. I’ll assume that the stylesheet is in static/css/SYNTAXSTYLESHEET.css.

Integrating it with Hugo

Now the hardest part, putting them into use with your Hugo project.

Add the syntax highlighter before the end of the document body (<body>) tag and the stylesheet inside the <head>.

The available location for it depends on the theme. I recommend to start looking to the layout folder with the default templates of the theme (theme/$NAME_OF_THEME/layouts/_default) then the partial folder (theme/$NAME_OF_THEME/layouts/partials).

Tip
You might want to start at the base template (theme/$NAME_OF_THEME/layouts/_default/baseof.html).

Copy the appropriate file from the theme folder to your own layout folder and link it similar to the following code listing.

<!-- Inside of the head element -->
<link rel="stylesheet" href="{{ "css/SYNTAXSTYLESHEET.css" | absURL }}">

<!-- ... -->

<!-- Before the end of the body tag -->
<script src="{{ "js/lib/SYNTAXHIGHLIGHT.js" | absURL }}"></script>

The setup is done! That leaves one less problem for content migration in case you want to move out of Hugo. You’ll thank yourself for doing so.

Problems with using the workflow

While Hugo and Asciidoctor is great and all, there are a couple of problems with this setup.

The most obvious is the HTML output of Asciidoctor with the default backend is not great and leaves a lot of things to be desired.

`<div>` then a `<p>` for a paragraph
Figure 1. <div> then a <p> for a paragraph, really?

It’s not semantic and it is unconventional. Not only that it’s a pain to style it with CSS but also breaks a lot of the accessibility features like screen readers since it relies on certain HTML tag structures.

Note
You can get around this by using the Asciidoctor HTML5s backend to produce the correct and semantic web-friendly output. Remember, the arguments passed from Hugo to Asciidoctor is hardcoded. You would have to trick Hugo somehow into passing your own arguments. Fortunately, you don’t need to worry since there’s already someone out there shared the details.

Another problem you could encounter (and maybe bash your head against) is the basic support for Asciidoctor itself if you don’t want to rely much on creating hacks and workarounds.

As previously mentioned, Hugo supports Asciidoctor through external helpers. External helpers are relatively new and more like an experimental feature. There is a proposal on improving it by adding user configurations so at least there’s hope for this particular feature to expand.

There’s also the fact that not all built-in feature of Hugo (such as table of contents) works within Asciidoctor (and possibly other non-Markdown formats) content. Fortunately, Asciidoctor is quite extensive by itself and there’s not a lot of Hugo features that doesn’t work and you won’t likely need them anyway.

Also, native Asciidoctor front matter doesn’t work as previously mentioned.

Deploying with Travis CI

Some posts are floating around on how to make a done-and-forget deployment toolchain with different tools.

Personally, I pass the full effort of deploying my blog to a CI/CD workflow. I use Travis CI for the job.

Note
You can also take a view on the GitHub repo of my blog for an idea how it works on a larger picture.

Here’s the configuration I’ve used to deploy my Hugo blog:

dist: bionic (1)
language: generic

before_install:
  - sudo apt-get update
  - sudo apt-get install ruby
  # Assuming that the GitHub API is at version 4.0 (2)
  - curl https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "hugo_extended.*deb" | grep "browser_download_url" | cut --delimiter=":" --delimiter="\"" --fields=4 | wget -qi -
  - sudo dpkg -i hugo*.deb
  - sudo gem install asciidoctor (3)
script:
  - hugo (4)
deploy: (5)
  local_dir: "public/"
  provider: pages
  skip_cleanup: true
  github_token: $GITHUB_TOKEN
  target_branch: gh-pages
  on:
    branch:
      - demo
      - master

Here’s the breakdown of the configuration:

  1. It will use a Linux-based machine with Ubuntu Bionic (18.04) as the operating system.

  2. Downloads the latest Hugo binary from its repo through GitHub release and installs it.

  3. Installs the Asciidoctor toolchain.

  4. Build the Hugo site.

  5. Deploy the build folder to the gh-pages branch of my GitHub repo when the branch occurred at demo or master.

Note
The configuration should work as long as the GitHub API version is at version 4. You may have to do a bit of API debugging and tweaking to get it right.

Depending on the web hosting service provider, you may have to do additional work such as pre-compressing your files or configuring your server. Since the blog is hosted using GitHub Pages, I don’t have to configure some stuff (unfortunately for me).

Conclusion

That’s all of the Hugo and Asciidoctor stuff you need to know for now.

Just look for more examples and you’ll get more idea. You can take the GitHub repo of my blog for a starter point.

Personally, blogging with Hugo and Asciidoctor sums up to be fun. So fun that I eventually created a theme that focuses on supporting Asciidoctor content along with other stuff.

Not perfect but it still offers a lot of satisfying and more expressive writing experience compared to writing with Markdown.

With all of the imperfections this workflow has, there’s some stuff to look forward in the future especially with Hugo’s external helpers feature. Hopefully, more tools will take notice of Asciidoctor and how it could be great for writing technical and web-based content.

Further looking

Web

Asciidoctor documentation

Getting started with Asciidoctor is quite easy with the official documentation. It should be able to help you a long way into getting comfortable with it. If you’re getting the ropes of it, I recommend to check out the user manual often.

Better Hugo/AsciiDoc HTML by David Gauer (ratfactor.com)

It’s a short and sweet post on how to make HTML output of Asciidoctor way better than before with the UNIX PATH trickery trick that I’ve mentioned in the article.

Hugo documentation

The documentation of Hugo is great. Has a lot of clear and concise information for newcomers and has an intuitive navigation of the content structure.

Video

Hugo tutorial series by Mike Dane

A video series by Mike Dane. It’s also featured on the official Hugo documentation as a video resource. The video series is well-done and offers brief and concise explanation.