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.
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.
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.
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+/1I4WEe1qG7IdH7Viu6zXTL4wikmJW1+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:
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