Better performance with Hugo Pipes
posted in performance, hugo
I recently used Hugo Pipes to optimize asset handling in this site and replaced old Webpack based hack I had used to achieve same.
That hack was done in the times of Hugo version 0.3x. Currently all necessary things like JS bundling, CSS pre- and post-processing, fingerprinting, minification, etc is possible with Hugo native features:
- Markdown render hooks for customizing how certain blocks in Markdown content is rendered
- Hugo Pipes for CSS / JS processing, bundling, fingerprinting, minification, image processing etc
- Page bundles for better organization of post related resources with Markdown content
Like you see in following snippets, Hugo Pipes version is really simple to use and maintain compared to configuring and handling Webpack builds. Essentially they work like you would expect with zero configuration.
Styles
Handling CSS is really simple:
{{ $style := resources.Get "stylesheets/all.css.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet"
href="{{ $style.Permalink }}"
integrity="{{ $style.Data.Integrity }}" />
Referenced CSS file could be stored in global /assets
directory, or in the
themes/{{ theme name}}/assets
.
In the above template function toCSS
and minify
does exactly what it says in
the tin, but fingerprint
is a bit more special enabling multiple things with
the content based hash it creates:
- It determines actual filename generated for the asset stored in the
public
directory after the build..Permalink
and.RelPermalink
provides urls with that hash fingerprint enabling cache busting when content changes. - It also calculates hash value which could be used as subresources integrity hash
Javascript
Bundling Javascript is just as easy as CSS above and template functions are used in similar fashion.
{{ $jsMain := resources.Get "javascripts/index.js" | js.Build "main.js" | minify | fingerprint }}
<script type="application/javascript"
src="{{ $jsMain.Permalink }}"
integrity="{{ $jsMain.Data.Integrity }}">
</script>
It uses esbuild under the hood and NPM
dependencies installed from package.json
with NPM or Yarn works just like one
would expect.
Images
Image handling needed a bit more work, but it’s also great example how powerful content rendering possibilities Hugo has.
Stuff I had to do:
- Store images in
/assets/images
directory, similar directory inside theme, or with the page bundle directory - Create image rendering hook to customize how regular Markdown image
references eg.
![alt text](file path / name)
are rendered. - Customize templates to use global
resources
or.Page.Resources
modules
Here’s the layouts/_default/_markup/render-image.html
implementation:
{{ $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) | fingerprint }}
{{ $alt := .PlainText | safeHTML }}
{{ $caption := "" }}
{{ with .Title }}
{{ $caption = . | safeHTML }}
{{ end }}
<figure>
<a href="{{ $image.RelPermalink }}">
<img
src="{{ $image.RelPermalink }}"
width="{{ $image.Width }}"
height="{{ $image.Height }}"
alt="{{ if $alt }}{{ $alt }}{{ else if $caption }}{{ $caption | markdownify | plainify }}{{ else }} {{ end }}"
/>
</a>
{{ with $caption }}
<figcaption>{{ . | markdownify }}</figcaption>
{{ end }}
</figure>
And another example how global images could be used in the layouts:
<div class="logo">
<a href="/" title="Home">
{{ with resources.Get "images/logo.jpg" }}
{{ $image := . | fingerprint }}
<img
src="{{ $image.RelPermalink }}"
width="{{ $image.Width }}"
height="{{ $image.Height }}"
alt="Logo"
/>
{{ end }}
</a>
</div>
I’m especially happy to be able to use regular Markdown for all images instead of custom shortcodes I earlies had to implement for that Webpack + manifest json version.