Tools, Guides

Master npm with Hugo: customizing build scripts

Master npm with Hugo

In a series of three posts I will introduce you to npm, show you how to manage dependencies, and show you how to customize build scripts. This is the third post of the series.

Master npm with Hugo: customizing build scripts

In the second post of the series, I showed you that Hugo itself can be seen as a dependency of your project, and that a starter theme provides you with full control over your dependencies and gives you the possibility to customize build scripts.

We also extended our basic example with a local Hugo install, a Hugo project structure, and a starter theme structure.

Customizing scripts

Now we’re ready to talk about how to customize build scripts. In stead of using npx to run a script from a local npm package, we’re going to add a number of custom scripts to our package.json:

{
  ..
  "scripts": {
    "prestart": "npm run clean",
    "start": "hugo server --disableFastRender",
    "prebuild": "npm run clean",
    "build": "hugo --minify",
    "clean": "shx rm -rf public resources",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ..
}

Note the pre scripts prestart and prebuild. The prestart script e.g. executes before the start script. Similarly, you can use post scripts. A poststart script e.g. would execute after the start script.

Listing scripts

The npm run command lists the scripts available in your package.json:

$ npm run
Lifecycle scripts included in npm-hugo:
  prestart
    npm run clean
  start
    hugo server --disableFastRender
  test
    echo "Error: no test specified" && exit 1

available via `npm run-script`:
  prebuild
    npm run clean
  build
    hugo --gc --minify
  clean
    shx rm -rf public resources

Running scripts

The npm run start command e.g. will:

  1. delete the temporary build directories, and next
  2. start the hugo server with the --disableFastRender flag enabled.

Bootstrap

We can use dependencies to pull in resources for our project. Let’s import the Bootstrap SCSS we added previously, into our starter theme:

shx mkdir assets && shx echo '@import \"bootstrap/scss/bootstrap\";' > ./assets/app.scss

And process the SCSS with Hugo in ./layouts/partials/head.html:

<head>
  {{ $options := (dict "targetPath" "main.css" "includePaths" (slice "node_modules")) -}}
  {{ $css := resources.Get "app.scss" | resources.ToCSS $options -}}
  <link rel="stylesheet" href="{{ $css.Permalink }}">
</head>

Let’s add some Bootstrap styling to ./layouts/index.html:

{{ define "main" }}
  <div class="container-fluid">
    <div class="row vh-100 align-items-center justify-content-center">
      <div class="col-md-7">
        <h1 class="text-center">{{ .Title }} πŸŽ‰</h1>
      </div>
    </div>
  </div>
{{ end }}

Add the resources and public folders to .gitignore, and check the result with npm run start.

Removing unused CSS

We can also use dependencies to pull in tooling for our build process. Let’s make our project production ready by stripping out unnecessary CSS:

PurgeCSS analyzes your content and your CSS files. Then it matches the selectors used in your files with the one in your content files. It removes unused selectors from your CSS, resulting in smaller CSS files

We will also be installing Autoprefixer, a PostCSS plugin to parse CSS and add vendor prefixes to CSS rules using values from Can I Use:

npm i -D @fullhuman/postcss-purgecss postcss-cli autoprefixer

Add ./postcss.config.js:

const autoprefixer = require('autoprefixer')
const purgecss = require('@fullhuman/postcss-purgecss')

module.exports = {
  plugins: [
    autoprefixer(),
    purgecss({
      content: ['./layouts/**/*.html']
    })
  ]
}

Update ./layouts/partials/head.html:

<head>
  {{ $options := (dict "targetPath" "main.css" "includePaths" (slice "node_modules")) -}}
  {{ $css := resources.Get "app.scss" | resources.ToCSS $options -}}
  {{ if hugo.IsProduction -}}
    {{ $css = $css | minify | fingerprint | resources.PostCSS | resources.PostProcess -}}
  {{ end -}}
  <link rel="stylesheet" href="{{ $css.Permalink }}">
</head>

Update ./config.toml:

baseURL = "/"
..

Add to package.json:

{
  ..
  "browserslist": [
    "defaults"
  ],
  ..
}

Run npm run build and check the result. That’s it. We’ve brought down the file size of our CSS file from 185.76 KB to β€” yes that’s right β€” 2.63 KB.

Plugins

We could also use dependencies to pull in extra functionality into our starter theme. Think e.g. of SEO, performance, and security.

Wrap up

You’re now familiar with customizing build scripts. We also extended our example with Bootstrap, and optimized it for use in production. You’re also aware that you could use dependencies to pull in extra functionality.

You can find the full example in the npm-hugo repo I’ve set up.

In case you’re curious, the idea for this series was taken from a similar post written about WordPress.

Hugo, Node.js, npm

  1. Master npm with Hugo: an introduction to npm
  2. Master npm with Hugo: managing dependencies
  3. Master npm with Hugo: customizing build scripts