Hey,

Hugo v0.43 just got released today (see the GitHub Releases page for Download links and notes about all that’s included in v0.43) with great news!

From my point of view, the most significant feature in this release (as noted in the gohugo.io website) is the introduction of the asset pipeline (Hugo Pipes), a way of allowing the user to specify transformations to be applied to assets (css, javascript, svg and more) right from the template.

Illustration of Hugo's asset pipeline

The tl;dr is illustrated in the release docs:

{{ $styles := resources.Get "scss/main.scss" |
        toCSS |
        postCSS |
        minify |
        fingerprint }}

<link   rel="stylesheet" 
        href="{{ $styles.Permalink }}" 
        integrity="{{ $styles.Data.Integrity }}" 
        media="screen">

As I was in the middle of refactoring this blog’s template to better support Accelerated Mobile Pages Project (AMP), this release came just in time.

You can check why this is useful for AMP below, but first, some intro.

What’s minification all about?

Although we usually code for humans (hopefully), placing a great deal of space between elements, adding comments and making either function names or variables verbose, the browser doesn’t really care about all of that.

For instance, consider the following Javascript file (491 bytes):

// maybeRemoveTrailingSlash - removes the trailing slash (`/`) 
// of a given string if it has one. Otherwise, nothing is done.
function maybeRemoveTrailingSlash(str) {
   // Perform an early termination if it doesn't match
   // the type of string that we're looking for.
   if (!str.endsWith("/")) {
      return str;
  }

  // Remove the last character.
  return str.substring(0, str.length - 1);
}

console.log(maybeRemoveTrailingSlash("a"));
console.log(maybeRemoveTrailingSlash("a/"));

When we consider the fact that every byte that you add in a given file needs to shipped via an unreliable network (that might be very expensive for the user in the case of Mobile phones), that’s us requiring our customers to not only wait longer, but pay more to get our content.

Illustration of the minification process

By minifying our assets we can remove all that’s needed for a human to understand the code (thus, effectively reducing its size), while still maintaining its semantics.

The code above could then become something like this:

// Minified - 
a=(n)=>n.endsWith("/")?n.substring(0,n.length-1):n
b=console.log
b(a("a"))
b(a("a/"))

That’s a reduction from 491 bytes to 85 bytes!

What about bundling?

Bundling allows us to take a series of files (for instance, base.css which specifies the base styling of our website, and syntax.css, which specifies coloring for of pre blocks) and concatenate them all such that the end result is a single file.

Illustration of the bundling (concatenation) process

Although nowadays that’s a practice that is not very useful anymore in many cases (see Musings on HTTP/2 and Bundling), there’s still AMP forcing us to adhere to such practices - all custom styles must be defined in the head section of the page within an <style amp-custom> element (see AMP - Supported CSS).

Hugo Pipes comes very handy for this.

The asset bundling and minification pipeline

Knowing that we have those two primitives to build our asset pipeline upon, we can go ahead and actually put it into code.

Having my _default/single.html (the template that defines the page of a single article) defined like this:

<!doctype html>
<html lang="en">
  <head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
    {{ partial "content/single/base_head.html" . }}
    {{ partial "content/single/ld.html" . }}
  </head>

  <body class="article">
    {{ partial "content/single/main.html" . }}

    {{ partial "footer.html" . }}
    {{ partial "style.html" . }}  <!-- THIS IS WHERE I'LL USE HUGO PIPES -->
    {{ partial "tracking.html" . }}
  </body>
</html>

I can then move on and define that partial (partials/style.html):

{{ $cssOpts := (dict "targetPath" "styles/syntax.css") }}
{{ $base := resources.Get "css/index.css" }}
{{ $syntax := resources.Get "css/syntax.css" }}

{{ $style := slice $base $syntax | resources.Concat "bundle.css" | minify | fingerprint }}

<link rel="stylesheet"
      href="{{ $style.Permalink }}"
      integrity="{{ $style.Data.Integrity }}">

ps.: the snippet above is horizontally scrollable.

Once an article gets rendered, that <link> element becomes what you might expect:

<link rel="stylesheet"
      href="https://ops.tips/bundle.min.e838f1fc0247d7eff52385847b5a86ec8747ed58aeeb35d32f8c22926256d7ec.css"
      integrity="sha256-6Djx/AJH1&#43;/1I4WEe1qG7IdH7Viu6zXTL4wikmJW1&#43;w=">

ps.: you can check this out by prepending view-source: to this article page and go to the end of the page if you’re using Chrome.

Making the minified bundle AMP ready

If you’re into AMP though, you know that what we did above doesn’t provide a valid AMP page.

That’s because you can’t make use of link elements unless it’s for specifying a custom font resource. We have to put the CSS code in the html file in the head section inside a style element. Period.

To do that, I make use of Hugo’s Custom Output Formats.

Just like I have a _default/single.html template, I create an extra _default/single.amp.html, in which I can then specify a different partial: style.amp.html.

<!doctype html>
<html amp lang="en">
  <head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
    {{ partial "content/single/base_head.html" . }}
    {{ partial "content/single/ld.html" . }}
    {{ partial "amp-boilerplate.html" . }}
    {{ partial "style.amp.html" }}  <!-- HERE'S THE STYLE PARTIAL -->
  </head>
  <body class="article">
    {{ partial "content/single/main.html" . }}

    {{ partial "footer.amp.html" . }}
    {{ partial "tracking.amp.html" . }}
  </body>
</html>

Properly placed in the head as AMP mandates, we then implement the partial:

{{ $cssOpts := (dict "targetPath" "styles/syntax.css") }}
{{ $base := resources.Get "css/index.css" }}
{{ $syntax := resources.Get "css/syntax.css" }}

{{ $style := slice $base $syntax | resources.Concat "bundle.css" | minify }}

<style amp-custom>
{{ $style.Content | safeCSS }}
</style>

Said and done, now we have inlined bundled and minified CSS for our AMP pages:

Evidence of Hugo minifying the CSS contents in the AMP page

Closing thoughts

Being a Hugo user for not a long time (since I started this blog in November 2017), Hugo has always been a piece of cake to deal with, allowing me to have my blog up and running without knowing a lot about it while still providing me a great deal of value as I learn more and more.

I’m delighted that this new release as it removes complexity - while you needed to do those optimizations and further templating yourself before, now it’s even easier (my Makefile just sent a Thanks").

If you have any questions or found something weird here, please let me know! I’m cirowrc on Twitter.

In case you want to receive updates about content like this, make sure you subscribe to the mailing list.

Have a good one!

Ciro