Adding javascript libraries to with Hugo Pipes

So you have made a static website using Hugo and you love the speed and simplicity of managing it. But you notice that your static site is a little too static. Using javascript you could make it a little more engaging.

Different ways to add javascript

There are multiple ways how one could to add javascript libraries to a Hugo site:

  • Including files from a CDN (content delivery network).

    Arguably the simplest approach. Simply add the tag

    <script src="www.cdn.com/script.js" />
    

    Most popular libraries are hosted on a CDN to allow speedy delivery to your visitors.

  • Manually downloading and installing the libraries.

    Download the library in the browser and place it in the site or theme static folder of Hugo. A common practice is to also have a vendor subfolder (and a folder for the library) to delimit your custom javascript for code you downloaded. Including the library is then done with the tag

    <script src="vendor/library/script.js" />
    
  • Installing libraries using a package manager

    Using a package manager such as npm or yarn to install libraries will require a little more work, but allows for easier management of the dependencies you are adding, e.g. getting a newer version of the library you are using.

    During javascript development, using these tools is the norm, but it does clash with Hugos “convention over configuration” paradigm. Installing libraries using these will put the code into a node_modules folder which Hugo will not include in your site. Normally in the frontend development toolkit you would have a build system and bundler, e.g. a combination of gulp, babel, webpack etc. These tools allow transforming newer javascript syntax to something even older browsers can use, allow bundling several files into one (less important after the introduction of HTTP/2) and allows minification (transforming human readable code to smaller, but still browser readable code).

Javascript with Hugo Pipes

In this article we will be using the last method, but instead of using a javascript build system, we will take advantage of Hugo Pipes, a decision with its own set of pros and cons.

Pros

  • No separate build system required, all is handled within Hugo.
  • Allows bundling and minification of several libraries into one file, which should be faster for the browser to load.

Cons

  • Build system is not as capable as the purpose built ones mentioned above and not all libraries may work if they require more processing during build/bundling.

Getting started

First we will initialize npm to create a package.json file which will store information on which javascript libraries we are adding to our site.

npm init -y

Note in which folder you run this command will depend on whether you want the libraries to be site or theme dependent. Then we add our javascript library using:

npm i package-name

A note on source control

Hopefully you are already using source control for your Hugo project. In such a case, you probably want to avoid checking in the contents of the node_modules folder, so be sure to add following to the .gitignore file:

# ignore imported javascript
node_modules/

Including javascript in Hugo

As mentioned above, Hugo applies convention over configuration and has no knowledge of what to do with the node_modules folder and hence we need a way to include the javascript libraries in our site. For this, we will be using Hugo Pipes and some less commonly used features of Hugo.

Hugo Pipes

Hugo Pipes allows processing of files similar to other frontend build systems and allows processing of files before they are included in your site, e.g. converting SASS to browser compatible CSS, minification etc.

Normally Hugo Pipes works with files from the assets folder though, using e.g. resources.Get "js/script.js" to get the script file from either the site or themes assets folder. As we want to get use file from node_modules, we need to use the readFile method to read the file and then create a resource using resources.FromString. With readFile we need to provide the path to the file from the Hugo main folder, so to load a script file from the node_modules folder into a resource we either use:

{{ $script := readFile "node_modules/package/script.js" | resources.FromString "js/script.js" }}

Or in the case of using the node_modules folder inside a theme:

{{ $script := readFile "themes/theme-name/node_modules/package/script.js" | resources.FromString "js/vendor.js" }}

To use this script in our site, we use the following tag in our Hugo template:

<script src="{{ $script.Permalink }}" />

Adding it all together and adding minification and fingerprinting (to make sure the browser has to reload the script file if its contents change) we have the following:

$script := readFile "themes/theme-name/node_modules/package/script.js" | resources.FromString "js/script.js" | resources.Minify | resources.Fingerprint
<script src="{{ $script.Permalink }}" />

Adding bundling and minification

The above solution works well for single files, but for multiple script files we will take advantage of bundling them up before minification as well as use Scratch to simplify specifying the files we want to bundle.

{{ $vendor := newScratch }}
{{ $vendor.Add "script" (readFile "themes/theme-name/node_modules/package1/script1.js") }}
{{ $vendor.Add "script" (readFile "themes/theme-name/node_modules/package2/script2.js") }}
{{ $js := $vendor.Get "script" | resources.FromString "js/vendor.js" | resources.Minify | resources.Fingerprint }}
<script src="{{ $js.Permalink }}"></script>

Here we are using the fact that Scratch.Add concatenates the string content we read from the different files and all of the bundled scripts will be made available as one resource at js/vendor.js, which we then minimize and fingerprint. In the end, the browser will see a script tag such as

<script src="/js/cfront.min.abcdef1234...1234.js" />

Conclusion

Using Hugo Pipes can be a simple way to include javascript libraries in your Hugo site, which in turn can turn your static site a little more dynamic. While it will not replace a full blown javascript build system, it comes for free with your Hugo installation.