GeekSocket Plug in and be Geekified

How to use Hugo Modules

I use Hugo for this site. I migrated to Hugo from WordPress a year ago. Hugo is a static site generator written in Go. Hugo project has frequent releases, so I usually update the version once in a few months. This involves reading all the release notes and making any changes to the theme if required. The theme changes are required rarely though.

While going through the release notes of v0.56.0, I noticed a new feature called Hugo Modules. It uses Go Modules underneath to manage the modules. I wanted to try out that feature but never managed to spend time on it. Finally, while working on PythonPune’s website, I tried Hugo Modules. It is awesome! Let’s see how you can use it for themes and other stuff like shortcodes, layouts and much more.

What is Hugo Modules

In a programming language we have dependency management tools to manage the libraries / packages. Hugo Modules serves a similar purpose for a Hugo site. The concept of modules comes from the Go programming language as Hugo Modules in actually powered by the Go Modules.

A static website can have dependencies like themes, some files like CSS or JavaScript and so on. We will take a look at more use cases later.

What qualifies as a Hugo module

A Hugo module is a directory which has subdirectories like layouts, static, data etc. These subdirectories hold different files of a Hugo site. Website’s root source directory as well as a theme is also a module.

These subdirectories are referred to as mounts in Hugo modules. content, static, layouts, data, assets, i18n and archetypes are the supported mounts.

Why should I use it?

Very basic use case of Hugo Modules is adding themes to the site. We don’t have to use git submodules or copy the theme files. This also enables versioning of the theme and pinning it to specific commit.

Same with the files we copy over for shortcodes, static files etc. We just have to add the module and that’s it, Hugo takes care of downloading the files and using them. This simplifies the automated deployment workflows.

With the ability to use only a specific directory or a file from a module, Hugo modules opens up the door to a lot of possibilities and combinations.

How can I define my site’s dependencies

The first and one time step is to initialize the site as a module. This can be done with hugo mod init <module path>.

To add a module as a dependency of a site, we have to run the command hugo mod get <module path>. Then add an entry for the module in site configuration (in a config.yaml or config.toml file).

The <module path> is required to be a valid Go module path like github.com/bep/docuapi. For local modules we don’t have to run the get command. We just need to keep them in the themesDir (default is themes) and add an entry for them.

The Go module path of the dependencies should resolve1 to a valid VCS repository like Git. It can be hosted anywhere like GitLab, GitHub etc. It may or may not have a go.mod file in it. Local Hugo modules don’t need to have a go.mod file if they don’t have any dependencies.

If your site’s source code is not pushed to a public VCS repository, the Go module path for the site can be a single word as well. Note that if the module name does not resolve to a VCS repository, it won’t be possible for others to use the site as a dependency.

Use cases of modules

Let’s take a look at some examples of site configurations using Hugo modules.

Adding a theme as a dependency

A major dependency of a site is a theme. We will add the github.com/tummychow/lanyon-hugo theme to our sample-site.

To initialize the site as a module and add the theme we run the following commands from the site’s root directory.

$ hugo mod init geeksocket.in/sample-site
go: creating new go.mod: module geeksocket.in/sample-site

$ hugo mod get github.com/tummychow/lanyon-hugo@master
go: downloading github.com/tummychow/lanyon-hugo v0.0.0-20200329195303-72aefc1d4844
go: github.com/tummychow/lanyon-hugo master => v0.0.0-20200329195303-72aefc1d4844

Add a new section in the configuration file,

# config.yaml
title: "My New Hugo Site"
# …
module:
  imports:
    - path: github.com/tummychow/lanyon-hugo

It is recommended to remove the theme option from the site configuration when using modules2.

Using mounts for specific files

Let’s add a layout file to our site which is a Markdown render hook. The module github.com/bep/portable-hugo-links is a complete site. We will just use a specific file from it.

$ hugo mod get github.com/bep/portable-hugo-links@latest
go: downloading github.com/bep/portable-hugo-links v0.5.3
go: github.com/bep/portable-hugo-links latest => v0.5.3

The site configuration,

module:
  imports:
    # …
    - path: github.com/bep/portable-hugo-links
      mounts:
        - source: layouts/_default/_markup/render-link.html
          target: layouts/_default/_markup/render-link.html

The above import specific mount section takes the render-link.html from the module and makes it available at the same path in our site.

We can mount any other files from our site’s directory on a different path within our site.

module:
  # …
  mounts:
  - source: README.md
    target: content/about/index.md

This will create a /about page using README.md file which is in the site root.

Using files from non Hugo repositories

We will add jQuery (jquery.js) to our site at static/js/jquery.js.

$ hugo mod get github.com/jquery/jquery-dist@3.5.1
go: downloading github.com/jquery/jquery-dist v0.0.0-20200504225046-4c0e4becb826
go: github.com/jquery/jquery-dist 3.5.1 => v0.0.0-20200504225046-4c0e4becb826

The site configuration,

module:
  imports:
    # …
    - path: github.com/jquery/jquery-dist
      mounts:
        - source: dist/jquery.js
          target: static/js/jquery.js

With this change, /js/jquery.js will be available on the site.

Combining multiple themes / modules

It is possible that a same file is present in two modules. In such cases, the file is taken from the module which appears first in the list.

Consider the following site configuration. Both gkskt-blogstarters and blogstarters have the file archetypes/first-post.md.

module:
  imports:
    - path: gkskt-blogstarters
    - path: blogstarters

The first-post.md is actually taken from gkskt-blogstarters in above case. This way we can combine multiple themes as well.

Other hints and references

Local module development

When making changes to a theme or module, we often want to test it with our site. The replace directive of Go Modules can be used.

We can add a line in the go.mod file like,

replace github.com/tummychow/lanyon-hugo => /home/bhavin/src/blog/lanyon-hugo

The filesystem path on the right side of => needs to have a go.mod file in it. It can be created with go mod init.

The same thing can be achieved with the module configuration option replacements and the environment variable HUGO_MODULE_REPLACEMENTS.

The Cache settings

Hugo uses /tmp directory to cache the downloaded modules. This may get cleaned on system reboot. To avoid this, it can be set to some other directory with the help of caches option in site configuration. Take a look at this comment by bep for more information.

References

  • The hugo mod get command has different ways to pull a specific version of a module. You might have noticed some of them in above examples. Checkout the go get section from Go Modules Reference.
  • An example site using Hugo Modules: bep/my-modular-site.
  • It is possible to vendor the module files in the site’s directory. Take a look at the Vendor Your Modules from the documentation.
  • Hugo Modules section from Hugo documentation.

  1. We can use custom domains which resolve to a hosted repository. This works with the help of the HTML tag <meta name="go-import" …>. Read more about it in the Remote import paths section of Go documentation. ↩︎

  2. The theme option is kept for backward compatibility. Take a look at this comment from bep. ↩︎


Comments

comments powered by Disqus