Building DUALUSE
Adventures in sunk-cost
Travis dropped
Publishing has always been a bit tricky for me; as a strategy to avoid generating content I hold some synthetic requirements near and dear. I look for, in any generator, control over the output - and minimalism. WordPress, 3rd party venues (Medium, etc) were definitely out. Previously this meant hand-rolling HTML and CSS from scratch for every page without the help of a generator. Thankfully, a colleague (thanks Parsia!) clued me in to Hugo - and I set about building a Hugo site the hard way.
Hugo’s quick start guide encourages you to select an existing template, and begin throwing down your content. Someone else’s CSS and HTML? Totally unacceptable.
Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris.
In This Guide
Read this post top-to-bottom to learn;
- How Hugo’s various components conspire to render a basic static site,
- How to work on your own Hugo template, or customize one off the shelf, and
- How to extend markdown rendering via Hugo Shortcodes.
I assume familiarity with programming concepts; variables, control flow, and markup/styling; Markdown, HTML, and CSS. I skip right over setting up a Hugo working directory and site, for that begin with the official quick start guide - and return when you’re ready to create your own templates.
Dev Goals
I needed clean and semantically correct HTML51, along with a tight stylesheet (from scratch; with help from MDN2, citizens of StackOverflow, ALA, and others), that rendered well across a variety of screens and in print. I wanted to spend my time fussing over fonts, and waste as much of my visitors bandwidth as possible loading them. In line with doing everything from scratch I wanted the freedom to amuse myself with bizarre details. Finally - no JavaScript3 - trackers - dynamic content - anything. Nope. HTML5 and CSS3 would be enough.
Building The Hugo Templates
Equipped with the essentials; caffeine, google, and the Hugo documentation I set about adopting a brand new theme called, creatively enough, sparse
- easy;
## hugo new theme sparse
This command generates a bare-bones theme in your Hugo working directory:
Structure
First up, I started prototyping a static page with dummy content that roughly matched the type of structure I was looking for:
Thanks to CSS3, and my personal hangups, I was able to keep to this structure, winding up with:
Let’s take a walk through sparse
and see how everything fits together.
Common Template Sections
Blackfriday generates pristine markup for the main article content, and all you have to do is get Hugo to render it to the right spot. In a Hugo template, you have a few essential components for content layout, the most fundamental being baseof.html
located in a theme’s ./_default/
directory.
Base Template
The base template renders the common frame for all content on your Hugo site - here’s mine;
You can spot 6 go-template functions, doing 5 discrete things, in the above sample;
{{- partial "head.html" . -}}
{{
and}}
declare the start and end of control, respectively.-
instructs rendering to collapse whitespace before or after, in this case both directions.partial
4 is a function that looks up the parameter in the filesystem for inclusion. This one loads my header content from./layouts/partials/head.html
.{{ .Permalink }}
- A function is optional - if you provide a bare reference to a variable, it’ll be rendered to HTML and inserted.
.Permalink
is a Hugo Page Variable5 that produces a canonical URL to the page being rendered. {{ partial "header.html" . }}
- This grabs DUALUSE’s banner / home-link from
./layouts/partials/header.html
. {{ block "main" . }}{{- end }}
- The
block
/end
pair declare a section of this document - that a later step can grab a handle on to inject content. In this case themain
block is populated with whatever Hugo thinks is a page’s core content. A content page fits the bill, but so do special list and index pages. Why doesblock
have a inner part? If Hugo didn’t replace themain
block, the page would be rendered with these contents as default. {{- partial "footer.html" . -}}
- FINALLY include
./layouts/partials/footer.html
.
Zooming out from baseof
, Hugo’s engine is iterating through various bits of content to render; the core layout
, defining a page’s common elements, is handled by baseof
, and depending on the type of content, main
is rendered by a content layout type page; list
, single
, index
, or 404
. After Hugo has a layout
it renders the content layout page into its block main
.
In the figure above, to the left sits Hugo - and the relevant components to rendering - in the middle rests the templates the engine knows to look for and to the right, data sources - either dynamic (variables) or other components (partials) from the filesystem.
Let’s explore the other partials
before getting into layout type main
sections.
Head
baseof
imports this section to define the contents of html>head
- we set meta tags, title, and stylesheets here.
We’ve already seen template variable resolution in baseof
, but control flow elements and the .Site
variables are new.
{{ .Summary }}
- A page variable that includes a Hugo generated summary of the page content.
{{ .Site.Title }}
.Site
contains Hugo Site Variables6 - populated, in part, from your Hugo site’sconfig.toml
..Title
, as expected is defined byconfig.toml
’stitle
setting..Params "description"
.Params
contains a dictionary of arbitrary values set in page content. In this case, if content’s front-matter7 setsdescription="Tight Description"
, you can access it with.Params.description
.{{- if (isset .Params "description") -}}
if
is a go template action8 that provides a conditional statement andisset
9 is a Hugo function testing the.Params
dictionary fordescription
.{{ if (STATEMENT) }} ... {{ end }}
- Is the correct formatting for a conditional block, which can be further extended;
{{ else if (STATEMENT)}}
&{{ else }}
- Can appear between an
{{ if ...}}
and its corresponding{{ end }}
.
The purpose of this block of logic around <meta name="description">
is to let us set its content as the contents of front-matter description
or summary
values, and fall back on the automatically generated Summary
page variable - and provide a general description if all else fails.
Header
We know what’s going on now, we’re generating a link base to the BaseURL
defined in config.toml
’s baseURL
setting.
Footer
The footer has some new constructs to review, go template pipelines10, go template variables11, and two new actions with
and range
. We’re using these to construct the bottom of the page - and list related articles.
.Site.RegularPages.Related
- This site variable resolves other Hugo pages that are considered ‘related’. How Hugo fills related pages is configurable in your site config12. This variable is special, it’s a collection.
.Site.Menus.main
- Another collection, this time containing references to URLs configured in the site config, or in page front-matter.
$related := .Site.RegularPages.Related
$
denotes the start of a name for a go template variable and:=
is the declare and assign operator.{{ $related := .Site.RegularPages.Related . | first 5 }}
- Using
|
is referred to as pipeline chaining, functions which consume and emit pipelines of compatible types can be chained together. Here, the callfirst 5
grabs the first 5 items from.Related
. It may not be obvious, but everything to the right of an assignment,:=
, including pipelines, gets evaluated first. {{ with ... }}
{{ end }}
with
sets the scope for the enclosed block to the provided argument, if the provided argument is empty, the whole block is skipped. As we use it here,with
, likeif
, supports an{{ else }}
block.{{ with $related }}
…{{ range .}}
…{{ end }}
…{{ end }}
range
will iterate over the provided collection, and execute the enclosed block with the current item’s scope. In this case, a single related page. In the top example,{{ range . }}
is iterating over the scope set with{{ with $related }}
,$related
.
Awesome, we’ve built a functional layout, and had a taste of working with templates. Next, we explore the various Hugo layout types that provide rendering for the main
block.
Content Sections
Content sections get loaded up depending on what type of context Hugo is trying to render. Let’s cover the three special layouts I’m using, Lists, Singles, and the Index.
List
When it’s not rendering individual content, like a post, Hugo spends most of its time generating lists of things. Lists of items sharing a category or tag, lists of content under a content section. Tons of lists.
You should be familiar with everything going on here. Hugo hands us a scope with .Data.Pages
populated with the list of things it wants us to list, and we range
through it to render individual items.
{{ define "main" -}}
…{{- end }}
- Hugo will hunt for a
block "main"
to drop this rendered content into. Declaring it here means we’ll get inserted intobaseof
. markdownify
13- Hugo runs whatever you pipe to this function through Blackfriday for rendering, providing pretty-printing of stuff specified in front matter variables.
Single
Here’s the main show; the Single Page Template.
Hugo has rendered a markdown file into .Content
using Blackfriday. Let’s just review the conditional here;
.WordCount
- A Hugo-populated page variable with the number of words found in
.Content
. {{ if and (gt .WordCount 400 ) (.Params.toc) }}
- Familiar infix notation for this is
if (.WordCount > 400) AND (.Params.toc)
.and
andgt
aren’t actually operators, they’re functions. We’re actually looking atif ( and(gt(.WordCount,400), .Params.toc))
, and this is a little clearer when you think of it that way. More than 400 words? Front matter setstoc=true
? Render a Table of Contents.
Index
I won’t bother - my ./index.html
is just a copy of the ./_default/list.html
template. You can be more adventurous and specify a totally unique and different Homepage Template. Maybe with some bio info.
Shortcodes
That’s it - we’re done with Hugo templates. Markdown and the extensions provided by Blackfriday cover most things you’d want to generate as content - but there are still a few holes in functionality. That’s where Hugo Shortcodes come in.
Shortcodes are provided by a template - and reside under ./layouts/shortcodes
as .html
files.
Most of my page content is going to contain some code - you can defintely use Markdown code fences, indents, or what-have-you for a pleasant experience - but I wanted all my code-blocks wrapped up as figure
elements with figcaption
s.
All you have to do to make this shortcode available is drop this in shortcodes/code.html
.
Hopefully you can see what’s going on from the usage example;
{{<code "The `code` shortcode.">}}
…{{</code>}}
- These open and close a shortcode - they kind of look like go’s template control blocks, but with XML style elements. Funky.
{{.Inner -}}
- This is whatever content appeared in between the shortcode block start and end. You can tell from the example above that
.Inner
contents get automatically processed through Blackfriday, so you can nest shortcodes and Markdown formatting. .Get 0
- This command grabs the first argument supplied in the shortcode opening statement - in our case,
The `code` shortcode.
. We pass this tomarkdownify
because, unlike.Inner
, these arguments aren’t processed by Blackfriday.
bc
provides blockquotes, like the one you saw at the top of the article. This shortcode takes a parameter specifying the quote credit, with the quote text in the body.
Which I use like;
Remember that context-aware encoding that go templates perform? Well, it was doing absolutely the wrong thing for the cite
attribute here - URL Encoding instead of HTML Entity Encoding.
{{$cite:=.Get 0 | htmlEscape}}
- HTML Entity Encode anything that might break out of the attribute value context. (I wouldn’t rely on
htmlEscape
to prevent injection attacks, and neither should you.) {{ printf "cite=%q" $cite | safeHTMLAttr }}
printf
is format strings; put$cite
intocite=%q
according to%q
.safeHTMLAttr
tells the engine that no further encoding of this value is necessary for the HTML Attribute context.
Wrap
You should have a better idea of how all the separate Hugo template components work together to create a site - I’ll run through DUALUSE’s styling in a later post.
If you see anything wrong in this post - or think something needs clarification, let me know and I’ll update it.