Bundling JavaScript in Hugo

Recent Hugo versions allow for built-in JavaScript (JS) bundling and minifying. This is something I wanted to use, and here’s how I did it.

Working against the theme

At the time of this writing, this site uses Hermit for its theme. Hermit minifies and fingerprints its own JS, but does not bundle any JS you include in layouts/partials/extra-foot.html.

Here’s the code in layouts/_default/baseof.html for loading extra-foot.html:

{{ $script := resources.Get "js/main.js" | minify | fingerprint -}}
<script src="{{ $script.Permalink }}" {{ printf "integrity=%q" $script.Data.Integrity | safeHTMLAttr }}></script>
{{- partial "analytics.html" . }}
{{- if templates.Exists "partials/extra-foot.html" -}}
{{ partial "extra-foot.html" . }}
{{- end }}

Any JS you include in extra-foot.html will be brought in as-is, as will the Google Analytics code if you use it.

Making the change

Based on this post in the Hugo Discourse forum, I added logic to provide additional JS files append have them minified as well.

In config.toml I added a a new parameter called customJS, which is an array of extra JavaScript files that should be loaded and minimized. Note that the Hermit theme’s provided JS main.js is not in the customJS list. In addition, custom JS files should be listed in the order they should be loaded. In my case, I am using the FontFaceObserver script which I call using ffo.js. If I listed ffo.js first, it would not work.

# Add custom JS, stored in the assets folder.
# The first entry, "js/main.js" comes from the theme.
# JS is added in the order shown.
customJS = ["js/fontfaceobserver.js", "js/ffo.js"]

For your JS files to be bundled, place them in your site’s assets folder. In my case, my folder structure looks like this. The js/main.js is not shown below as it is referenced from the Hermit theme folder. However, it would be in my own assets folder if I modified it.

+ assets
  + js

I then updated my copy of baseof.html to build on the main.js minifying but also added in any files referenced from customJS. If your theme’s or “main” JS file is called something different, use that name instead.

The below template code replaces all of the template code shown above.

{{ $jsslice := resources.Get "js/main.js" | slice -}}
{{ range .Site.Params.customJS -}}
{{ $jsslice = $jsslice | append (resources.Get .) -}}
{{ end -}}
{{ $js := $jsslice | resources.Concat "js/bundle.js" | minify | fingerprint -}}
<script src="{{ $js.Permalink }}" {{ printf "integrity=%q" $js.Data.Integrity | safeHTMLAttr }}></script>
{{- partial "analytics.html" . }}
{{- if templates.Exists "partials/extra-foot.html" -}}
{{ partial "extra-foot.html" . }}
{{- end }}

NOTE: If you need to load any scripts that should not be minimized, continue to use the extra-foot.html file. Also, I did not include Google Analytics in JS minifying.